]> git.saurik.com Git - wxWidgets.git/blame - src/stc/scintilla/src/Document.cxx
wxMessageBox off the main thread lost result code.
[wxWidgets.git] / src / stc / scintilla / src / Document.cxx
CommitLineData
9ce192d4 1// Scintilla source code edit control
65ec6247
RD
2/** @file Document.cxx
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
4 **/
1dcf666d 5// Copyright 1998-2011 by Neil Hodgson <neilh@scintilla.org>
9ce192d4
RD
6// The License.txt file describes the conditions under which this software may be distributed.
7
8#include <stdlib.h>
9#include <string.h>
10#include <stdio.h>
11#include <ctype.h>
1dcf666d
RD
12#include <assert.h>
13
14#include <string>
15#include <vector>
9ce192d4
RD
16
17#include "Platform.h"
18
1dcf666d 19#include "ILexer.h"
9ce192d4 20#include "Scintilla.h"
1dcf666d 21
7e0c58e9
RD
22#include "SplitVector.h"
23#include "Partitioning.h"
24#include "RunStyles.h"
9ce192d4 25#include "CellBuffer.h"
9e96e16f 26#include "PerLine.h"
b8193d80 27#include "CharClassify.h"
1dcf666d 28#include "CharacterSet.h"
7e0c58e9 29#include "Decoration.h"
9ce192d4 30#include "Document.h"
65ec6247 31#include "RESearch.h"
1dcf666d 32#include "UniConversion.h"
65ec6247 33
7e0c58e9
RD
34#ifdef SCI_NAMESPACE
35using namespace Scintilla;
36#endif
37
9e730a78
RD
38static inline bool IsPunctuation(char ch) {
39 return isascii(ch) && ispunct(ch);
40}
41
1dcf666d
RD
42void LexInterface::Colourise(int start, int end) {
43 if (pdoc && instance && !performingStyle) {
44 // Protect against reentrance, which may occur, for example, when
45 // fold points are discovered while performing styling and the folding
46 // code looks for child lines which may trigger styling.
47 performingStyle = true;
9e730a78 48
1dcf666d
RD
49 int lengthDoc = pdoc->Length();
50 if (end == -1)
51 end = lengthDoc;
52 int len = end - start;
53
54 PLATFORM_ASSERT(len >= 0);
55 PLATFORM_ASSERT(start + len <= lengthDoc);
9e730a78 56
1dcf666d
RD
57 int styleStart = 0;
58 if (start > 0)
59 styleStart = pdoc->StyleAt(start - 1) & pdoc->stylingBitsMask;
60
61 if (len > 0) {
62 instance->Lex(start, len, styleStart, pdoc);
63 instance->Fold(start, len, styleStart, pdoc);
64 }
65
66 performingStyle = false;
67 }
9e730a78
RD
68}
69
9ce192d4
RD
70Document::Document() {
71 refCount = 0;
1dcf666d 72#ifdef _WIN32
9ce192d4 73 eolMode = SC_EOL_CRLF;
1dcf666d
RD
74#else
75 eolMode = SC_EOL_LF;
9ce192d4
RD
76#endif
77 dbcsCodePage = 0;
78 stylingBits = 5;
79 stylingBitsMask = 0x1F;
9ce192d4 80 stylingMask = 0;
9ce192d4 81 endStyled = 0;
1a2fb4cd 82 styleClock = 0;
7e0c58e9
RD
83 enteredModification = 0;
84 enteredStyling = 0;
f6bcfd97 85 enteredReadOnlyCount = 0;
9ce192d4 86 tabInChars = 8;
f6bcfd97 87 indentInChars = 0;
591d01be 88 actualIndentInChars = 8;
f6bcfd97 89 useTabs = true;
65ec6247
RD
90 tabIndents = true;
91 backspaceUnindents = false;
9ce192d4
RD
92 watchers = 0;
93 lenWatchers = 0;
65ec6247
RD
94
95 matchesValid = false;
9e96e16f
RD
96 regex = 0;
97
1dcf666d
RD
98 UTF8BytesOfLeadInitialise();
99
9e96e16f
RD
100 perLineData[ldMarkers] = new LineMarkers();
101 perLineData[ldLevels] = new LineLevels();
102 perLineData[ldState] = new LineState();
103 perLineData[ldMargin] = new LineAnnotation();
104 perLineData[ldAnnotation] = new LineAnnotation();
105
106 cb.SetPerLine(this);
1dcf666d
RD
107
108 pli = 0;
9ce192d4
RD
109}
110
111Document::~Document() {
112 for (int i = 0; i < lenWatchers; i++) {
113 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
114 }
115 delete []watchers;
9e96e16f
RD
116 for (int j=0; j<ldSize; j++) {
117 delete perLineData[j];
118 perLineData[j] = 0;
119 }
9ce192d4
RD
120 watchers = 0;
121 lenWatchers = 0;
9e96e16f
RD
122 delete regex;
123 regex = 0;
1dcf666d
RD
124 delete pli;
125 pli = 0;
9e96e16f
RD
126}
127
128void Document::Init() {
129 for (int j=0; j<ldSize; j++) {
130 if (perLineData[j])
131 perLineData[j]->Init();
132 }
133}
134
135void Document::InsertLine(int line) {
136 for (int j=0; j<ldSize; j++) {
137 if (perLineData[j])
138 perLineData[j]->InsertLine(line);
139 }
140}
141
142void Document::RemoveLine(int line) {
143 for (int j=0; j<ldSize; j++) {
144 if (perLineData[j])
145 perLineData[j]->RemoveLine(line);
146 }
9ce192d4
RD
147}
148
149// Increase reference count and return its previous value.
150int Document::AddRef() {
151 return refCount++;
152}
153
d134f170 154// Decrease reference count and return its previous value.
9ce192d4 155// Delete the document if reference count reaches zero.
1dcf666d 156int SCI_METHOD Document::Release() {
9ce192d4
RD
157 int curRefCount = --refCount;
158 if (curRefCount == 0)
159 delete this;
160 return curRefCount;
161}
162
163void Document::SetSavePoint() {
164 cb.SetSavePoint();
165 NotifySavePoint(true);
166}
167
1dcf666d
RD
168int Document::GetMark(int line) {
169 return static_cast<LineMarkers *>(perLineData[ldMarkers])->MarkValue(line);
170}
171
172int Document::MarkerNext(int lineStart, int mask) const {
173 return static_cast<LineMarkers *>(perLineData[ldMarkers])->MarkerNext(lineStart, mask);
9e96e16f
RD
174}
175
65ec6247 176int Document::AddMark(int line, int markerNum) {
1dcf666d
RD
177 if (line >= 0 && line <= LinesTotal()) {
178 int prev = static_cast<LineMarkers *>(perLineData[ldMarkers])->
9e96e16f
RD
179 AddMark(line, markerNum, LinesTotal());
180 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
181 NotifyModified(mh);
182 return prev;
183 } else {
184 return 0;
185 }
64a3ee5f
ES
186}
187
1e9bafca 188void Document::AddMarkSet(int line, int valueSet) {
1dcf666d
RD
189 if (line < 0 || line > LinesTotal()) {
190 return;
191 }
1e9bafca
RD
192 unsigned int m = valueSet;
193 for (int i = 0; m; i++, m >>= 1)
194 if (m & 1)
1dcf666d 195 static_cast<LineMarkers *>(perLineData[ldMarkers])->
9e96e16f 196 AddMark(line, i, LinesTotal());
1e9bafca 197 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
1e9bafca
RD
198 NotifyModified(mh);
199}
200
65ec6247 201void Document::DeleteMark(int line, int markerNum) {
1dcf666d 202 static_cast<LineMarkers *>(perLineData[ldMarkers])->DeleteMark(line, markerNum, false);
1e9bafca 203 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
f6bcfd97 204 NotifyModified(mh);
88b780d9
RD
205}
206
65ec6247 207void Document::DeleteMarkFromHandle(int markerHandle) {
1dcf666d 208 static_cast<LineMarkers *>(perLineData[ldMarkers])->DeleteMarkFromHandle(markerHandle);
f6bcfd97 209 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
1e9bafca 210 mh.line = -1;
f6bcfd97
BP
211 NotifyModified(mh);
212}
213
65ec6247 214void Document::DeleteAllMarks(int markerNum) {
1dcf666d 215 bool someChanges = false;
9e96e16f 216 for (int line = 0; line < LinesTotal(); line++) {
1dcf666d
RD
217 if (static_cast<LineMarkers *>(perLineData[ldMarkers])->DeleteMark(line, markerNum, true))
218 someChanges = true;
219 }
220 if (someChanges) {
221 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
222 mh.line = -1;
223 NotifyModified(mh);
9e96e16f 224 }
f6bcfd97
BP
225}
226
1dcf666d
RD
227int Document::LineFromHandle(int markerHandle) {
228 return static_cast<LineMarkers *>(perLineData[ldMarkers])->LineFromHandle(markerHandle);
9e96e16f
RD
229}
230
1dcf666d 231int SCI_METHOD Document::LineStart(int line) const {
f6bcfd97
BP
232 return cb.LineStart(line);
233}
234
7e0c58e9 235int Document::LineEnd(int line) const {
9ce192d4 236 if (line == LinesTotal() - 1) {
f6bcfd97 237 return LineStart(line + 1);
9ce192d4 238 } else {
f6bcfd97 239 int position = LineStart(line + 1) - 1;
9ce192d4
RD
240 // When line terminator is CR+LF, may need to go back one more
241 if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
242 position--;
243 }
f6bcfd97 244 return position;
9ce192d4 245 }
f6bcfd97
BP
246}
247
1dcf666d
RD
248void SCI_METHOD Document::SetErrorStatus(int status) {
249 // Tell the watchers the lexer has changed.
250 for (int i = 0; i < lenWatchers; i++) {
251 watchers[i].watcher->NotifyErrorOccurred(this, watchers[i].userData, status);
252 }
253}
254
255int SCI_METHOD Document::LineFromPosition(int pos) const {
f6bcfd97
BP
256 return cb.LineFromPosition(pos);
257}
258
9e96e16f 259int Document::LineEndPosition(int position) const {
f6bcfd97 260 return LineEnd(LineFromPosition(position));
9ce192d4
RD
261}
262
9e96e16f
RD
263bool Document::IsLineEndPosition(int position) const {
264 return LineEnd(LineFromPosition(position)) == position;
265}
266
267int Document::VCHomePosition(int position) const {
9ce192d4
RD
268 int line = LineFromPosition(position);
269 int startPosition = LineStart(line);
9e96e16f 270 int endLine = LineEnd(line);
9ce192d4 271 int startText = startPosition;
1dcf666d 272 while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t'))
9ce192d4
RD
273 startText++;
274 if (position == startText)
275 return startPosition;
276 else
277 return startText;
278}
279
1dcf666d
RD
280int SCI_METHOD Document::SetLevel(int line, int level) {
281 int prev = static_cast<LineLevels *>(perLineData[ldLevels])->SetLevel(line, level, LinesTotal());
9ce192d4 282 if (prev != level) {
65ec6247 283 DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
7e0c58e9 284 LineStart(line), 0, 0, 0, line);
9ce192d4
RD
285 mh.foldLevelNow = level;
286 mh.foldLevelPrev = prev;
287 NotifyModified(mh);
288 }
289 return prev;
290}
291
1dcf666d
RD
292int SCI_METHOD Document::GetLevel(int line) const {
293 return static_cast<LineLevels *>(perLineData[ldLevels])->GetLevel(line);
9e96e16f
RD
294}
295
1dcf666d
RD
296void Document::ClearLevels() {
297 static_cast<LineLevels *>(perLineData[ldLevels])->ClearLevels();
9e96e16f
RD
298}
299
9ce192d4
RD
300static bool IsSubordinate(int levelStart, int levelTry) {
301 if (levelTry & SC_FOLDLEVELWHITEFLAG)
302 return true;
65ec6247 303 else
9ce192d4
RD
304 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
305}
306
1dcf666d 307int Document::GetLastChild(int lineParent, int level, int lastLine) {
9ce192d4
RD
308 if (level == -1)
309 level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
310 int maxLine = LinesTotal();
1dcf666d 311 int lookLastLine = (lastLine != -1) ? Platform::Minimum(LinesTotal() - 1, lastLine) : -1;
9ce192d4 312 int lineMaxSubord = lineParent;
65ec6247
RD
313 while (lineMaxSubord < maxLine - 1) {
314 EnsureStyledTo(LineStart(lineMaxSubord + 2));
315 if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))
f6bcfd97 316 break;
1dcf666d
RD
317 if ((lookLastLine != -1) && (lineMaxSubord >= lookLastLine) && !(GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG))
318 break;
9ce192d4
RD
319 lineMaxSubord++;
320 }
321 if (lineMaxSubord > lineParent) {
65ec6247
RD
322 if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) {
323 // Have chewed up some whitespace that belongs to a parent so seek back
324 if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) {
9ce192d4
RD
325 lineMaxSubord--;
326 }
327 }
328 }
329 return lineMaxSubord;
330}
331
332int Document::GetFoldParent(int line) {
591d01be 333 int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK;
65ec6247 334 int lineLook = line - 1;
9ce192d4 335 while ((lineLook > 0) && (
65ec6247
RD
336 (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
337 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
338 ) {
9ce192d4
RD
339 lineLook--;
340 }
341 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
65ec6247 342 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
9ce192d4
RD
343 return lineLook;
344 } else {
345 return -1;
346 }
347}
348
1dcf666d
RD
349void Document::GetHighlightDelimiters(HighlightDelimiter &highlightDelimiter, int line, int lastLine) {
350 int level = GetLevel(line);
351 int lookLastLine = Platform::Maximum(line, lastLine) + 1;
352
353 int lookLine = line;
354 int lookLineLevel = level;
355 int lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK;
356 while ((lookLine > 0) && ((lookLineLevel & SC_FOLDLEVELWHITEFLAG) ||
357 ((lookLineLevel & SC_FOLDLEVELHEADERFLAG) && (lookLineLevelNum >= (GetLevel(lookLine + 1) & SC_FOLDLEVELNUMBERMASK))))) {
358 lookLineLevel = GetLevel(--lookLine);
359 lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK;
360 }
361
362 int beginFoldBlock = (lookLineLevel & SC_FOLDLEVELHEADERFLAG) ? lookLine : GetFoldParent(lookLine);
363 if (beginFoldBlock == -1) {
364 highlightDelimiter.Clear();
365 return;
366 }
367
368 int endFoldBlock = GetLastChild(beginFoldBlock, -1, lookLastLine);
369 int firstChangeableLineBefore = -1;
370 if (endFoldBlock < line) {
371 lookLine = beginFoldBlock - 1;
372 lookLineLevel = GetLevel(lookLine);
373 lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK;
374 while ((lookLine >= 0) && (lookLineLevelNum >= SC_FOLDLEVELBASE)) {
375 if (lookLineLevel & SC_FOLDLEVELHEADERFLAG) {
376 if (GetLastChild(lookLine, -1, lookLastLine) == line) {
377 beginFoldBlock = lookLine;
378 endFoldBlock = line;
379 firstChangeableLineBefore = line - 1;
380 }
381 }
382 if ((lookLine > 0) && (lookLineLevelNum == SC_FOLDLEVELBASE) && ((GetLevel(lookLine - 1) & SC_FOLDLEVELNUMBERMASK) > lookLineLevelNum))
383 break;
384 lookLineLevel = GetLevel(--lookLine);
385 lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK;
386 }
387 }
388 if (firstChangeableLineBefore == -1) {
389 for (lookLine = line - 1, lookLineLevel = GetLevel(lookLine), lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK;
390 lookLine >= beginFoldBlock;
391 lookLineLevel = GetLevel(--lookLine), lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK) {
392 if ((lookLineLevel & SC_FOLDLEVELWHITEFLAG) || (lookLineLevelNum > (level & SC_FOLDLEVELNUMBERMASK))) {
393 firstChangeableLineBefore = lookLine;
394 break;
395 }
396 }
397 }
398 if (firstChangeableLineBefore == -1)
399 firstChangeableLineBefore = beginFoldBlock - 1;
400
401 int firstChangeableLineAfter = -1;
402 for (lookLine = line + 1, lookLineLevel = GetLevel(lookLine), lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK;
403 lookLine <= endFoldBlock;
404 lookLineLevel = GetLevel(++lookLine), lookLineLevelNum = lookLineLevel & SC_FOLDLEVELNUMBERMASK) {
405 if ((lookLineLevel & SC_FOLDLEVELHEADERFLAG) && (lookLineLevelNum < (GetLevel(lookLine + 1) & SC_FOLDLEVELNUMBERMASK))) {
406 firstChangeableLineAfter = lookLine;
407 break;
408 }
409 }
410 if (firstChangeableLineAfter == -1)
411 firstChangeableLineAfter = endFoldBlock + 1;
412
413 highlightDelimiter.beginFoldBlock = beginFoldBlock;
414 highlightDelimiter.endFoldBlock = endFoldBlock;
415 highlightDelimiter.firstChangeableLineBefore = firstChangeableLineBefore;
416 highlightDelimiter.firstChangeableLineAfter = firstChangeableLineAfter;
417}
418
9ce192d4
RD
419int Document::ClampPositionIntoDocument(int pos) {
420 return Platform::Clamp(pos, 0, Length());
421}
422
423bool Document::IsCrLf(int pos) {
424 if (pos < 0)
425 return false;
426 if (pos >= (Length() - 1))
427 return false;
428 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
429}
430
f6bcfd97 431int Document::LenChar(int pos) {
9e730a78
RD
432 if (pos < 0) {
433 return 1;
434 } else if (IsCrLf(pos)) {
f6bcfd97
BP
435 return 2;
436 } else if (SC_CP_UTF8 == dbcsCodePage) {
1dcf666d
RD
437 const unsigned char leadByte = static_cast<unsigned char>(cb.CharAt(pos));
438 const int widthCharBytes = UTF8BytesOfLead[leadByte];
f6bcfd97 439 int lengthDoc = Length();
1dcf666d
RD
440 if ((pos + widthCharBytes) > lengthDoc)
441 return lengthDoc - pos;
65ec6247 442 else
1dcf666d 443 return widthCharBytes;
9e730a78 444 } else if (dbcsCodePage) {
1dcf666d 445 return IsDBCSLeadByte(cb.CharAt(pos)) ? 2 : 1;
f6bcfd97
BP
446 } else {
447 return 1;
448 }
449}
1e9bafca 450
1dcf666d
RD
451bool Document::InGoodUTF8(int pos, int &start, int &end) const {
452 int trail = pos;
453 while ((trail>0) && (pos-trail < UTF8MaxBytes) && UTF8IsTrailByte(static_cast<unsigned char>(cb.CharAt(trail-1))))
454 trail--;
455 start = (trail > 0) ? trail-1 : trail;
7e0c58e9 456
1dcf666d
RD
457 const unsigned char leadByte = static_cast<unsigned char>(cb.CharAt(start));
458 const int widthCharBytes = UTF8BytesOfLead[leadByte];
459 if (widthCharBytes == 1) {
7e0c58e9
RD
460 return false;
461 } else {
1dcf666d
RD
462 int trailBytes = widthCharBytes - 1;
463 int len = pos - start;
7e0c58e9
RD
464 if (len > trailBytes)
465 // pos too far from lead
466 return false;
1dcf666d
RD
467 char charBytes[UTF8MaxBytes] = {static_cast<char>(leadByte),0,0,0};
468 for (int b=1; b<widthCharBytes && ((start+b) < Length()); b++)
469 charBytes[b] = cb.CharAt(static_cast<int>(start+b));
470 int utf8status = UTF8Classify(reinterpret_cast<const unsigned char *>(charBytes), widthCharBytes);
471 if (utf8status & UTF8MaskInvalid)
472 return false;
473 end = start + widthCharBytes;
7e0c58e9
RD
474 return true;
475 }
476}
477
9ce192d4
RD
478// Normalise a position so that it is not halfway through a two byte character.
479// This can occur in two situations -
480// When lines are terminated with \r\n pairs which should be treated as one character.
481// When displaying DBCS text such as Japanese.
482// If moving, move the position in the indicated direction.
483int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
484 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
e14d10b0
RD
485 // If out of range, just return minimum/maximum value.
486 if (pos <= 0)
487 return 0;
488 if (pos >= Length())
489 return Length();
9ce192d4 490
1e9bafca 491 // PLATFORM_ASSERT(pos > 0 && pos < Length());
9ce192d4
RD
492 if (checkLineEnd && IsCrLf(pos - 1)) {
493 if (moveDir > 0)
494 return pos + 1;
495 else
496 return pos - 1;
497 }
498
9ce192d4 499 if (dbcsCodePage) {
f6bcfd97
BP
500 if (SC_CP_UTF8 == dbcsCodePage) {
501 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
1dcf666d
RD
502 // If ch is not a trail byte then pos is valid intercharacter position
503 if (UTF8IsTrailByte(ch)) {
504 int startUTF = pos;
505 int endUTF = pos;
506 if (InGoodUTF8(pos, startUTF, endUTF)) {
507 // ch is a trail byte within a UTF-8 character
508 if (moveDir > 0)
509 pos = endUTF;
510 else
511 pos = startUTF;
512 }
513 // Else invalid UTF-8 so return position of isolated trail byte
f6bcfd97
BP
514 }
515 } else {
516 // Anchor DBCS calculations at start of line because start of line can
517 // not be a DBCS trail byte.
1dcf666d
RD
518 int posStartLine = LineStart(LineFromPosition(pos));
519 if (pos == posStartLine)
520 return pos;
521
522 // Step back until a non-lead-byte is found.
523 int posCheck = pos;
524 while ((posCheck > posStartLine) && IsDBCSLeadByte(cb.CharAt(posCheck-1)))
525 posCheck--;
9e730a78 526
1dcf666d
RD
527 // Check from known start of character.
528 while (posCheck < pos) {
529 int mbsize = IsDBCSLeadByte(cb.CharAt(posCheck)) ? 2 : 1;
e14d10b0 530 if (posCheck + mbsize == pos) {
9e730a78 531 return pos;
e14d10b0 532 } else if (posCheck + mbsize > pos) {
9e730a78 533 if (moveDir > 0) {
e14d10b0 534 return posCheck + mbsize;
9e730a78 535 } else {
e14d10b0 536 return posCheck;
9e730a78
RD
537 }
538 }
e14d10b0 539 posCheck += mbsize;
f6bcfd97 540 }
9ce192d4
RD
541 }
542 }
9ce192d4
RD
543
544 return pos;
545}
546
1dcf666d
RD
547// NextPosition moves between valid positions - it can not handle a position in the middle of a
548// multi-byte character. It is used to iterate through text more efficiently than MovePositionOutsideChar.
549// A \r\n pair is treated as two characters.
550int Document::NextPosition(int pos, int moveDir) const {
551 // If out of range, just return minimum/maximum value.
552 int increment = (moveDir > 0) ? 1 : -1;
553 if (pos + increment <= 0)
554 return 0;
555 if (pos + increment >= Length())
556 return Length();
557
558 if (dbcsCodePage) {
559 if (SC_CP_UTF8 == dbcsCodePage) {
560 if (increment == 1) {
561 // Simple forward movement case so can avoid some checks
562 const unsigned char leadByte = static_cast<unsigned char>(cb.CharAt(pos));
563 if (UTF8IsAscii(leadByte)) {
564 // Single byte character or invalid
565 pos++;
566 } else {
567 const int widthCharBytes = UTF8BytesOfLead[leadByte];
568 char charBytes[UTF8MaxBytes] = {static_cast<char>(leadByte),0,0,0};
569 for (int b=1; b<widthCharBytes; b++)
570 charBytes[b] = cb.CharAt(static_cast<int>(pos+b));
571 int utf8status = UTF8Classify(reinterpret_cast<const unsigned char *>(charBytes), widthCharBytes);
572 if (utf8status & UTF8MaskInvalid)
573 pos++;
574 else
575 pos += utf8status & UTF8MaskWidth;
576 }
577 } else {
578 // Examine byte before position
579 pos--;
580 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
581 // If ch is not a trail byte then pos is valid intercharacter position
582 if (UTF8IsTrailByte(ch)) {
583 // If ch is a trail byte in a valid UTF-8 character then return start of character
584 int startUTF = pos;
585 int endUTF = pos;
586 if (InGoodUTF8(pos, startUTF, endUTF)) {
587 pos = startUTF;
588 }
589 // Else invalid UTF-8 so return position of isolated trail byte
590 }
591 }
592 } else {
593 if (moveDir > 0) {
594 int mbsize = IsDBCSLeadByte(cb.CharAt(pos)) ? 2 : 1;
595 pos += mbsize;
596 if (pos > Length())
597 pos = Length();
598 } else {
599 // Anchor DBCS calculations at start of line because start of line can
600 // not be a DBCS trail byte.
601 int posStartLine = LineStart(LineFromPosition(pos));
602 // See http://msdn.microsoft.com/en-us/library/cc194792%28v=MSDN.10%29.aspx
603 // http://msdn.microsoft.com/en-us/library/cc194790.aspx
604 if ((pos - 1) <= posStartLine) {
605 return pos - 1;
606 } else if (IsDBCSLeadByte(cb.CharAt(pos - 1))) {
607 // Must actually be trail byte
608 return pos - 2;
609 } else {
610 // Otherwise, step back until a non-lead-byte is found.
611 int posTemp = pos - 1;
612 while (posStartLine <= --posTemp && IsDBCSLeadByte(cb.CharAt(posTemp)))
613 ;
614 // Now posTemp+1 must point to the beginning of a character,
615 // so figure out whether we went back an even or an odd
616 // number of bytes and go back 1 or 2 bytes, respectively.
617 return (pos - 1 - ((pos - posTemp) & 1));
618 }
619 }
620 }
621 } else {
622 pos += increment;
623 }
624
625 return pos;
626}
627
628bool Document::NextCharacter(int &pos, int moveDir) {
629 // Returns true if pos changed
630 int posNext = NextPosition(pos, moveDir);
631 if (posNext == pos) {
632 return false;
633 } else {
634 pos = posNext;
635 return true;
636 }
637}
638
639int SCI_METHOD Document::CodePage() const {
640 return dbcsCodePage;
641}
642
643bool SCI_METHOD Document::IsDBCSLeadByte(char ch) const {
644 // Byte ranges found in Wikipedia articles with relevant search strings in each case
645 unsigned char uch = static_cast<unsigned char>(ch);
646 switch (dbcsCodePage) {
647 case 932:
648 // Shift_jis
649 return ((uch >= 0x81) && (uch <= 0x9F)) ||
650 ((uch >= 0xE0) && (uch <= 0xFC));
651 // Lead bytes F0 to FC may be a Microsoft addition.
652 case 936:
653 // GBK
654 return (uch >= 0x81) && (uch <= 0xFE);
655 case 949:
656 // Korean Wansung KS C-5601-1987
657 return (uch >= 0x81) && (uch <= 0xFE);
658 case 950:
659 // Big5
660 return (uch >= 0x81) && (uch <= 0xFE);
661 case 1361:
662 // Korean Johab KS C-5601-1992
663 return
664 ((uch >= 0x84) && (uch <= 0xD3)) ||
665 ((uch >= 0xD8) && (uch <= 0xDE)) ||
666 ((uch >= 0xE0) && (uch <= 0xF9));
667 }
668 return false;
669}
670
671static inline bool IsSpaceOrTab(int ch) {
672 return ch == ' ' || ch == '\t';
673}
674
675// Need to break text into segments near lengthSegment but taking into
676// account the encoding to not break inside a UTF-8 or DBCS character
677// and also trying to avoid breaking inside a pair of combining characters.
678// The segment length must always be long enough (more than 4 bytes)
679// so that there will be at least one whole character to make a segment.
680// For UTF-8, text must consist only of valid whole characters.
681// In preference order from best to worst:
682// 1) Break after space
683// 2) Break before punctuation
684// 3) Break after whole character
685
686int Document::SafeSegment(const char *text, int length, int lengthSegment) {
687 if (length <= lengthSegment)
688 return length;
689 int lastSpaceBreak = -1;
690 int lastPunctuationBreak = -1;
691 int lastEncodingAllowedBreak = -1;
692 for (int j=0; j < lengthSegment;) {
693 unsigned char ch = static_cast<unsigned char>(text[j]);
694 if (j > 0) {
695 if (IsSpaceOrTab(text[j - 1]) && !IsSpaceOrTab(text[j])) {
696 lastSpaceBreak = j;
697 }
698 if (ch < 'A') {
699 lastPunctuationBreak = j;
700 }
701 }
702 lastEncodingAllowedBreak = j;
703
704 if (dbcsCodePage == SC_CP_UTF8) {
705 j += UTF8BytesOfLead[ch];
706 } else if (dbcsCodePage) {
707 j += IsDBCSLeadByte(ch) ? 2 : 1;
708 } else {
709 j++;
710 }
711 }
712 if (lastSpaceBreak >= 0) {
713 return lastSpaceBreak;
714 } else if (lastPunctuationBreak >= 0) {
715 return lastPunctuationBreak;
716 }
717 return lastEncodingAllowedBreak;
718}
719
9ce192d4
RD
720void Document::ModifiedAt(int pos) {
721 if (endStyled > pos)
722 endStyled = pos;
723}
724
1e9bafca
RD
725void Document::CheckReadOnly() {
726 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
727 enteredReadOnlyCount++;
728 NotifyModifyAttempt();
729 enteredReadOnlyCount--;
730 }
731}
732
7e0c58e9 733// Document only modified by gateways DeleteChars, InsertString, Undo, Redo, and SetStyleAt.
9ce192d4
RD
734// SetStyleAt does not change the persistent state of a document
735
a834585d 736bool Document::DeleteChars(int pos, int len) {
1dcf666d 737 if (len <= 0)
a834585d 738 return false;
65ec6247 739 if ((pos + len) > Length())
a834585d 740 return false;
1e9bafca 741 CheckReadOnly();
7e0c58e9 742 if (enteredModification != 0) {
a834585d
RD
743 return false;
744 } else {
7e0c58e9 745 enteredModification++;
9ce192d4 746 if (!cb.IsReadOnly()) {
f6bcfd97 747 NotifyModified(
65ec6247
RD
748 DocModification(
749 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
750 pos, len,
751 0, 0));
9ce192d4
RD
752 int prevLinesTotal = LinesTotal();
753 bool startSavePoint = cb.IsSavePoint();
7e0c58e9
RD
754 bool startSequence = false;
755 const char *text = cb.DeleteChars(pos, len, startSequence);
9ce192d4
RD
756 if (startSavePoint && cb.IsCollectingUndo())
757 NotifySavePoint(!startSavePoint);
65ec6247
RD
758 if ((pos < Length()) || (pos == 0))
759 ModifiedAt(pos);
760 else
761 ModifiedAt(pos-1);
f6bcfd97 762 NotifyModified(
65ec6247 763 DocModification(
7e0c58e9 764 SC_MOD_DELETETEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),
65ec6247
RD
765 pos, len,
766 LinesTotal() - prevLinesTotal, text));
9ce192d4 767 }
7e0c58e9 768 enteredModification--;
9ce192d4 769 }
a834585d 770 return !cb.IsReadOnly();
9ce192d4
RD
771}
772
591d01be 773/**
7e0c58e9 774 * Insert a string with a length.
591d01be 775 */
7e0c58e9
RD
776bool Document::InsertString(int position, const char *s, int insertLength) {
777 if (insertLength <= 0) {
778 return false;
779 }
1e9bafca 780 CheckReadOnly();
7e0c58e9 781 if (enteredModification != 0) {
a834585d
RD
782 return false;
783 } else {
7e0c58e9 784 enteredModification++;
9ce192d4 785 if (!cb.IsReadOnly()) {
f6bcfd97 786 NotifyModified(
65ec6247
RD
787 DocModification(
788 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
7e0c58e9 789 position, insertLength,
88a8b04e 790 0, s));
9ce192d4
RD
791 int prevLinesTotal = LinesTotal();
792 bool startSavePoint = cb.IsSavePoint();
7e0c58e9
RD
793 bool startSequence = false;
794 const char *text = cb.InsertString(position, s, insertLength, startSequence);
9ce192d4
RD
795 if (startSavePoint && cb.IsCollectingUndo())
796 NotifySavePoint(!startSavePoint);
7e0c58e9 797 ModifiedAt(position);
f6bcfd97 798 NotifyModified(
65ec6247 799 DocModification(
7e0c58e9
RD
800 SC_MOD_INSERTTEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),
801 position, insertLength,
65ec6247 802 LinesTotal() - prevLinesTotal, text));
9ce192d4 803 }
7e0c58e9 804 enteredModification--;
9ce192d4 805 }
a834585d 806 return !cb.IsReadOnly();
9ce192d4
RD
807}
808
1dcf666d
RD
809int SCI_METHOD Document::AddData(char *data, int length) {
810 try {
811 int position = Length();
812 InsertString(position,data, length);
813 } catch (std::bad_alloc &) {
814 return SC_STATUS_BADALLOC;
815 } catch (...) {
816 return SC_STATUS_FAILURE;
817 }
818 return 0;
819}
820
821void * SCI_METHOD Document::ConvertToDocument() {
822 return this;
823}
824
9ce192d4 825int Document::Undo() {
1e9bafca
RD
826 int newPos = -1;
827 CheckReadOnly();
7e0c58e9
RD
828 if (enteredModification == 0) {
829 enteredModification++;
1e9bafca
RD
830 if (!cb.IsReadOnly()) {
831 bool startSavePoint = cb.IsSavePoint();
832 bool multiLine = false;
833 int steps = cb.StartUndo();
834 //Platform::DebugPrintf("Steps=%d\n", steps);
1dcf666d
RD
835 int coalescedRemovePos = -1;
836 int coalescedRemoveLen = 0;
837 int prevRemoveActionPos = -1;
838 int prevRemoveActionLen = 0;
1e9bafca
RD
839 for (int step = 0; step < steps; step++) {
840 const int prevLinesTotal = LinesTotal();
841 const Action &action = cb.GetUndoStep();
842 if (action.at == removeAction) {
843 NotifyModified(DocModification(
844 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
9e96e16f
RD
845 } else if (action.at == containerAction) {
846 DocModification dm(SC_MOD_CONTAINER | SC_PERFORMED_UNDO);
847 dm.token = action.position;
848 NotifyModified(dm);
1dcf666d
RD
849 if (!action.mayCoalesce) {
850 coalescedRemovePos = -1;
851 coalescedRemoveLen = 0;
852 prevRemoveActionPos = -1;
853 prevRemoveActionLen = 0;
854 }
1e9bafca
RD
855 } else {
856 NotifyModified(DocModification(
857 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
858 }
859 cb.PerformUndoStep();
9e96e16f 860 if (action.at != containerAction) {
1dcf666d
RD
861 ModifiedAt(action.position);
862 newPos = action.position;
9e96e16f 863 }
1e9bafca
RD
864
865 int modFlags = SC_PERFORMED_UNDO;
866 // With undo, an insertion action becomes a deletion notification
867 if (action.at == removeAction) {
868 newPos += action.lenData;
869 modFlags |= SC_MOD_INSERTTEXT;
1dcf666d
RD
870 if ((coalescedRemoveLen > 0) &&
871 (action.position == prevRemoveActionPos || action.position == (prevRemoveActionPos + prevRemoveActionLen))) {
872 coalescedRemoveLen += action.lenData;
873 newPos = coalescedRemovePos + coalescedRemoveLen;
874 } else {
875 coalescedRemovePos = action.position;
876 coalescedRemoveLen = action.lenData;
877 }
878 prevRemoveActionPos = action.position;
879 prevRemoveActionLen = action.lenData;
9e96e16f 880 } else if (action.at == insertAction) {
1e9bafca 881 modFlags |= SC_MOD_DELETETEXT;
1dcf666d
RD
882 coalescedRemovePos = -1;
883 coalescedRemoveLen = 0;
884 prevRemoveActionPos = -1;
885 prevRemoveActionLen = 0;
1e9bafca
RD
886 }
887 if (steps > 1)
888 modFlags |= SC_MULTISTEPUNDOREDO;
889 const int linesAdded = LinesTotal() - prevLinesTotal;
890 if (linesAdded != 0)
891 multiLine = true;
892 if (step == steps - 1) {
893 modFlags |= SC_LASTSTEPINUNDOREDO;
894 if (multiLine)
895 modFlags |= SC_MULTILINEUNDOREDO;
896 }
1dcf666d 897 NotifyModified(DocModification(modFlags, action.position, action.lenData,
1e9bafca 898 linesAdded, action.data));
9ce192d4 899 }
65ec6247 900
1e9bafca
RD
901 bool endSavePoint = cb.IsSavePoint();
902 if (startSavePoint != endSavePoint)
903 NotifySavePoint(endSavePoint);
904 }
7e0c58e9 905 enteredModification--;
9ce192d4
RD
906 }
907 return newPos;
908}
909
910int Document::Redo() {
1e9bafca
RD
911 int newPos = -1;
912 CheckReadOnly();
7e0c58e9
RD
913 if (enteredModification == 0) {
914 enteredModification++;
1e9bafca
RD
915 if (!cb.IsReadOnly()) {
916 bool startSavePoint = cb.IsSavePoint();
917 bool multiLine = false;
918 int steps = cb.StartRedo();
919 for (int step = 0; step < steps; step++) {
920 const int prevLinesTotal = LinesTotal();
921 const Action &action = cb.GetRedoStep();
922 if (action.at == insertAction) {
923 NotifyModified(DocModification(
924 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
9e96e16f
RD
925 } else if (action.at == containerAction) {
926 DocModification dm(SC_MOD_CONTAINER | SC_PERFORMED_REDO);
927 dm.token = action.position;
928 NotifyModified(dm);
1e9bafca
RD
929 } else {
930 NotifyModified(DocModification(
931 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
932 }
933 cb.PerformRedoStep();
9e96e16f
RD
934 if (action.at != containerAction) {
935 ModifiedAt(action.position);
936 newPos = action.position;
937 }
1e9bafca
RD
938
939 int modFlags = SC_PERFORMED_REDO;
940 if (action.at == insertAction) {
941 newPos += action.lenData;
942 modFlags |= SC_MOD_INSERTTEXT;
9e96e16f 943 } else if (action.at == removeAction) {
1e9bafca
RD
944 modFlags |= SC_MOD_DELETETEXT;
945 }
946 if (steps > 1)
947 modFlags |= SC_MULTISTEPUNDOREDO;
948 const int linesAdded = LinesTotal() - prevLinesTotal;
949 if (linesAdded != 0)
950 multiLine = true;
951 if (step == steps - 1) {
952 modFlags |= SC_LASTSTEPINUNDOREDO;
953 if (multiLine)
954 modFlags |= SC_MULTILINEUNDOREDO;
955 }
956 NotifyModified(
957 DocModification(modFlags, action.position, action.lenData,
958 linesAdded, action.data));
9ce192d4 959 }
65ec6247 960
1e9bafca
RD
961 bool endSavePoint = cb.IsSavePoint();
962 if (startSavePoint != endSavePoint)
963 NotifySavePoint(endSavePoint);
964 }
7e0c58e9 965 enteredModification--;
9ce192d4
RD
966 }
967 return newPos;
968}
969
591d01be
RD
970/**
971 * Insert a single character.
972 */
a834585d 973bool Document::InsertChar(int pos, char ch) {
7e0c58e9 974 char chs[1];
9ce192d4 975 chs[0] = ch;
7e0c58e9 976 return InsertString(pos, chs, 1);
9ce192d4
RD
977}
978
591d01be
RD
979/**
980 * Insert a null terminated string.
981 */
7e0c58e9 982bool Document::InsertCString(int position, const char *s) {
1dcf666d 983 return InsertString(position, s, static_cast<int>(s ? strlen(s) : 0));
9ce192d4
RD
984}
985
f6bcfd97
BP
986void Document::ChangeChar(int pos, char ch) {
987 DeleteChars(pos, 1);
988 InsertChar(pos, ch);
989}
990
9ce192d4 991void Document::DelChar(int pos) {
f6bcfd97 992 DeleteChars(pos, LenChar(pos));
9ce192d4
RD
993}
994
a834585d 995void Document::DelCharBack(int pos) {
9ce192d4 996 if (pos <= 0) {
a834585d 997 return;
9ce192d4
RD
998 } else if (IsCrLf(pos - 2)) {
999 DeleteChars(pos - 2, 2);
9e730a78 1000 } else if (dbcsCodePage) {
1dcf666d 1001 int startChar = NextPosition(pos, -1);
f6bcfd97 1002 DeleteChars(startChar, pos - startChar);
9ce192d4
RD
1003 } else {
1004 DeleteChars(pos - 1, 1);
9ce192d4
RD
1005 }
1006}
1007
f6bcfd97
BP
1008static int NextTab(int pos, int tabSize) {
1009 return ((pos / tabSize) + 1) * tabSize;
1010}
1011
1dcf666d
RD
1012static std::string CreateIndentation(int indent, int tabSize, bool insertSpaces) {
1013 std::string indentation;
f6bcfd97 1014 if (!insertSpaces) {
1dcf666d
RD
1015 while (indent >= tabSize) {
1016 indentation += '\t';
f6bcfd97 1017 indent -= tabSize;
9ce192d4 1018 }
f6bcfd97 1019 }
1dcf666d
RD
1020 while (indent > 0) {
1021 indentation += ' ';
f6bcfd97 1022 indent--;
f6bcfd97 1023 }
1dcf666d 1024 return indentation;
f6bcfd97
BP
1025}
1026
1dcf666d 1027int SCI_METHOD Document::GetLineIndentation(int line) {
f6bcfd97
BP
1028 int indent = 0;
1029 if ((line >= 0) && (line < LinesTotal())) {
1030 int lineStart = LineStart(line);
1031 int length = Length();
1dcf666d 1032 for (int i = lineStart; i < length; i++) {
f6bcfd97
BP
1033 char ch = cb.CharAt(i);
1034 if (ch == ' ')
1035 indent++;
1036 else if (ch == '\t')
1037 indent = NextTab(indent, tabInChars);
65ec6247 1038 else
f6bcfd97 1039 return indent;
9ce192d4
RD
1040 }
1041 }
f6bcfd97
BP
1042 return indent;
1043}
1044
1045void Document::SetLineIndentation(int line, int indent) {
1046 int indentOfLine = GetLineIndentation(line);
1047 if (indent < 0)
1048 indent = 0;
1049 if (indent != indentOfLine) {
1dcf666d 1050 std::string linebuf = CreateIndentation(indent, tabInChars, !useTabs);
f6bcfd97
BP
1051 int thisLineStart = LineStart(line);
1052 int indentPos = GetLineIndentPosition(line);
9e96e16f 1053 UndoGroup ug(this);
f6bcfd97 1054 DeleteChars(thisLineStart, indentPos - thisLineStart);
1dcf666d 1055 InsertCString(thisLineStart, linebuf.c_str());
f6bcfd97
BP
1056 }
1057}
1058
7e0c58e9 1059int Document::GetLineIndentPosition(int line) const {
65ec6247
RD
1060 if (line < 0)
1061 return 0;
f6bcfd97
BP
1062 int pos = LineStart(line);
1063 int length = Length();
1dcf666d 1064 while ((pos < length) && IsSpaceOrTab(cb.CharAt(pos))) {
f6bcfd97
BP
1065 pos++;
1066 }
1067 return pos;
1068}
1069
d134f170
RD
1070int Document::GetColumn(int pos) {
1071 int column = 0;
1072 int line = LineFromPosition(pos);
1073 if ((line >= 0) && (line < LinesTotal())) {
1dcf666d 1074 for (int i = LineStart(line); i < pos;) {
d134f170 1075 char ch = cb.CharAt(i);
1a2fb4cd 1076 if (ch == '\t') {
d134f170 1077 column = NextTab(column, tabInChars);
1a2fb4cd
RD
1078 i++;
1079 } else if (ch == '\r') {
d134f170 1080 return column;
1a2fb4cd 1081 } else if (ch == '\n') {
d134f170 1082 return column;
7e0c58e9
RD
1083 } else if (i >= Length()) {
1084 return column;
1a2fb4cd 1085 } else {
d134f170 1086 column++;
1dcf666d 1087 i = NextPosition(i, 1);
1a2fb4cd 1088 }
d134f170
RD
1089 }
1090 }
1091 return column;
1092}
1093
1dcf666d
RD
1094int Document::CountCharacters(int startPos, int endPos) {
1095 startPos = MovePositionOutsideChar(startPos, 1, false);
1096 endPos = MovePositionOutsideChar(endPos, -1, false);
1097 int count = 0;
1098 int i = startPos;
1099 while (i < endPos) {
1100 count++;
1101 if (IsCrLf(i))
1102 i++;
1103 i = NextPosition(i, 1);
1104 }
1105 return count;
1106}
1107
1a2fb4cd
RD
1108int Document::FindColumn(int line, int column) {
1109 int position = LineStart(line);
1a2fb4cd 1110 if ((line >= 0) && (line < LinesTotal())) {
9e96e16f 1111 int columnCurrent = 0;
a33203cb 1112 while ((columnCurrent < column) && (position < Length())) {
1a2fb4cd
RD
1113 char ch = cb.CharAt(position);
1114 if (ch == '\t') {
1115 columnCurrent = NextTab(columnCurrent, tabInChars);
1dcf666d
RD
1116 if (columnCurrent > column)
1117 return position;
1a2fb4cd
RD
1118 position++;
1119 } else if (ch == '\r') {
1120 return position;
1121 } else if (ch == '\n') {
1122 return position;
1123 } else {
1124 columnCurrent++;
1dcf666d 1125 position = NextPosition(position, 1);
1a2fb4cd
RD
1126 }
1127 }
1128 }
1129 return position;
1130}
1131
f6bcfd97
BP
1132void Document::Indent(bool forwards, int lineBottom, int lineTop) {
1133 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
1134 for (int line = lineBottom; line >= lineTop; line--) {
1135 int indentOfLine = GetLineIndentation(line);
1e9bafca
RD
1136 if (forwards) {
1137 if (LineStart(line) < LineEnd(line)) {
1138 SetLineIndentation(line, indentOfLine + IndentSize());
1139 }
1140 } else {
f6bcfd97 1141 SetLineIndentation(line, indentOfLine - IndentSize());
1e9bafca 1142 }
f6bcfd97 1143 }
9ce192d4
RD
1144}
1145
a33203cb
RD
1146// Convert line endings for a piece of text to a particular mode.
1147// Stop at len or when a NUL is found.
1148// Caller must delete the returned pointer.
1dcf666d 1149char *Document::TransformLineEnds(int *pLenOut, const char *s, size_t len, int eolModeWanted) {
a33203cb
RD
1150 char *dest = new char[2 * len + 1];
1151 const char *sptr = s;
1152 char *dptr = dest;
1153 for (size_t i = 0; (i < len) && (*sptr != '\0'); i++) {
1154 if (*sptr == '\n' || *sptr == '\r') {
1dcf666d 1155 if (eolModeWanted == SC_EOL_CR) {
a33203cb 1156 *dptr++ = '\r';
1dcf666d 1157 } else if (eolModeWanted == SC_EOL_LF) {
a33203cb 1158 *dptr++ = '\n';
1dcf666d 1159 } else { // eolModeWanted == SC_EOL_CRLF
a33203cb
RD
1160 *dptr++ = '\r';
1161 *dptr++ = '\n';
1162 }
1163 if ((*sptr == '\r') && (i+1 < len) && (*(sptr+1) == '\n')) {
1164 i++;
1165 sptr++;
1166 }
1167 sptr++;
1168 } else {
1169 *dptr++ = *sptr++;
1170 }
1171 }
1172 *dptr++ = '\0';
1173 *pLenOut = (dptr - dest) - 1;
1174 return dest;
1175}
1176
9ce192d4 1177void Document::ConvertLineEnds(int eolModeSet) {
9e96e16f 1178 UndoGroup ug(this);
a33203cb 1179
9ce192d4
RD
1180 for (int pos = 0; pos < Length(); pos++) {
1181 if (cb.CharAt(pos) == '\r') {
1e9bafca 1182 if (cb.CharAt(pos + 1) == '\n') {
a33203cb
RD
1183 // CRLF
1184 if (eolModeSet == SC_EOL_CR) {
1185 DeleteChars(pos + 1, 1); // Delete the LF
1186 } else if (eolModeSet == SC_EOL_LF) {
1187 DeleteChars(pos, 1); // Delete the CR
9ce192d4
RD
1188 } else {
1189 pos++;
1190 }
1e9bafca 1191 } else {
a33203cb 1192 // CR
9ce192d4 1193 if (eolModeSet == SC_EOL_CRLF) {
a33203cb 1194 InsertString(pos + 1, "\n", 1); // Insert LF
9ce192d4 1195 pos++;
a33203cb
RD
1196 } else if (eolModeSet == SC_EOL_LF) {
1197 InsertString(pos, "\n", 1); // Insert LF
1198 DeleteChars(pos + 1, 1); // Delete CR
9ce192d4
RD
1199 }
1200 }
a33203cb
RD
1201 } else if (cb.CharAt(pos) == '\n') {
1202 // LF
1203 if (eolModeSet == SC_EOL_CRLF) {
1204 InsertString(pos, "\r", 1); // Insert CR
1205 pos++;
1206 } else if (eolModeSet == SC_EOL_CR) {
1207 InsertString(pos, "\r", 1); // Insert CR
1208 DeleteChars(pos + 1, 1); // Delete LF
1209 }
9ce192d4
RD
1210 }
1211 }
a33203cb 1212
9ce192d4
RD
1213}
1214
7e0c58e9 1215bool Document::IsWhiteLine(int line) const {
1e9bafca
RD
1216 int currentChar = LineStart(line);
1217 int endLine = LineEnd(line);
1218 while (currentChar < endLine) {
1219 if (cb.CharAt(currentChar) != ' ' && cb.CharAt(currentChar) != '\t') {
1220 return false;
1221 }
1222 ++currentChar;
9e730a78 1223 }
1e9bafca 1224 return true;
9e730a78
RD
1225}
1226
1227int Document::ParaUp(int pos) {
1228 int line = LineFromPosition(pos);
1229 line--;
1e9bafca 1230 while (line >= 0 && IsWhiteLine(line)) { // skip empty lines
9e730a78
RD
1231 line--;
1232 }
1e9bafca 1233 while (line >= 0 && !IsWhiteLine(line)) { // skip non-empty lines
9e730a78
RD
1234 line--;
1235 }
1236 line++;
1237 return LineStart(line);
1238}
1239
1e9bafca
RD
1240int Document::ParaDown(int pos) {
1241 int line = LineFromPosition(pos);
1242 while (line < LinesTotal() && !IsWhiteLine(line)) { // skip non-empty lines
1243 line++;
1244 }
1245 while (line < LinesTotal() && IsWhiteLine(line)) { // skip empty lines
1246 line++;
1247 }
1248 if (line < LinesTotal())
1249 return LineStart(line);
1250 else // end of a document
1251 return LineEnd(line-1);
1252}
1253
b8193d80 1254CharClassify::cc Document::WordCharClass(unsigned char ch) {
1dcf666d 1255 if ((SC_CP_UTF8 == dbcsCodePage) && (!UTF8IsAscii(ch)))
b8193d80
RD
1256 return CharClassify::ccWord;
1257 return charClass.GetClass(ch);
9ce192d4
RD
1258}
1259
1a2fb4cd
RD
1260/**
1261 * Used by commmands that want to select whole words.
1262 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
1263 */
1264int Document::ExtendWordSelect(int pos, int delta, bool onlyWordCharacters) {
b8193d80 1265 CharClassify::cc ccStart = CharClassify::ccWord;
9ce192d4 1266 if (delta < 0) {
1a2fb4cd
RD
1267 if (!onlyWordCharacters)
1268 ccStart = WordCharClass(cb.CharAt(pos-1));
1269 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart))
9ce192d4
RD
1270 pos--;
1271 } else {
7e0c58e9 1272 if (!onlyWordCharacters && pos < Length())
1a2fb4cd
RD
1273 ccStart = WordCharClass(cb.CharAt(pos));
1274 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
9ce192d4
RD
1275 pos++;
1276 }
1dcf666d 1277 return MovePositionOutsideChar(pos, delta, true);
9ce192d4
RD
1278}
1279
1a2fb4cd 1280/**
9e730a78 1281 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
1a2fb4cd
RD
1282 * (delta < 0).
1283 * This is looking for a transition between character classes although there is also some
1284 * additional movement to transit white space.
1285 * Used by cursor movement by word commands.
1286 */
9ce192d4
RD
1287int Document::NextWordStart(int pos, int delta) {
1288 if (delta < 0) {
b8193d80 1289 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace))
9ce192d4 1290 pos--;
1a2fb4cd 1291 if (pos > 0) {
b8193d80 1292 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));
1a2fb4cd 1293 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) {
9ce192d4 1294 pos--;
1a2fb4cd 1295 }
9ce192d4
RD
1296 }
1297 } else {
b8193d80 1298 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));
1a2fb4cd 1299 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
9ce192d4 1300 pos++;
b8193d80 1301 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace))
9ce192d4
RD
1302 pos++;
1303 }
1304 return pos;
1305}
1306
8e54aaed
RD
1307/**
1308 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
1309 * (delta < 0).
1310 * This is looking for a transition between character classes although there is also some
1311 * additional movement to transit white space.
1312 * Used by cursor movement by word commands.
1313 */
1314int Document::NextWordEnd(int pos, int delta) {
1315 if (delta < 0) {
1316 if (pos > 0) {
b8193d80
RD
1317 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));
1318 if (ccStart != CharClassify::ccSpace) {
8e54aaed
RD
1319 while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) {
1320 pos--;
1321 }
1322 }
b8193d80 1323 while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace) {
8e54aaed
RD
1324 pos--;
1325 }
1326 }
1327 } else {
b8193d80 1328 while (pos < Length() && WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace) {
8e54aaed
RD
1329 pos++;
1330 }
1331 if (pos < Length()) {
b8193d80 1332 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));
8e54aaed
RD
1333 while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) {
1334 pos++;
1335 }
1336 }
1337 }
1338 return pos;
1339}
1340
65ec6247 1341/**
1a2fb4cd
RD
1342 * Check that the character at the given position is a word or punctuation character and that
1343 * the previous character is of a different character class.
65ec6247 1344 */
d134f170
RD
1345bool Document::IsWordStartAt(int pos) {
1346 if (pos > 0) {
b8193d80
RD
1347 CharClassify::cc ccPos = WordCharClass(CharAt(pos));
1348 return (ccPos == CharClassify::ccWord || ccPos == CharClassify::ccPunctuation) &&
1a2fb4cd 1349 (ccPos != WordCharClass(CharAt(pos - 1)));
9ce192d4 1350 }
d134f170
RD
1351 return true;
1352}
1353
65ec6247 1354/**
1a2fb4cd
RD
1355 * Check that the character at the given position is a word or punctuation character and that
1356 * the next character is of a different character class.
65ec6247 1357 */
d134f170 1358bool Document::IsWordEndAt(int pos) {
a33203cb 1359 if (pos < Length()) {
b8193d80
RD
1360 CharClassify::cc ccPrev = WordCharClass(CharAt(pos-1));
1361 return (ccPrev == CharClassify::ccWord || ccPrev == CharClassify::ccPunctuation) &&
1a2fb4cd 1362 (ccPrev != WordCharClass(CharAt(pos)));
9ce192d4
RD
1363 }
1364 return true;
1365}
1366
65ec6247 1367/**
9e730a78 1368 * Check that the given range is has transitions between character classes at both
1a2fb4cd 1369 * ends and where the characters on the inside are word or punctuation characters.
65ec6247 1370 */
d134f170
RD
1371bool Document::IsWordAt(int start, int end) {
1372 return IsWordStartAt(start) && IsWordEndAt(end);
1373}
1374
65ec6247
RD
1375static inline char MakeLowerCase(char ch) {
1376 if (ch < 'A' || ch > 'Z')
1377 return ch;
1378 else
1379 return static_cast<char>(ch - 'A' + 'a');
1380}
1381
1dcf666d
RD
1382CaseFolderTable::CaseFolderTable() {
1383 for (size_t iChar=0; iChar<sizeof(mapping); iChar++) {
1384 mapping[iChar] = static_cast<char>(iChar);
1385 }
1386}
1387
1388CaseFolderTable::~CaseFolderTable() {
1389}
1390
1391size_t CaseFolderTable::Fold(char *folded, size_t sizeFolded, const char *mixed, size_t lenMixed) {
1392 if (lenMixed > sizeFolded) {
1393 return 0;
1394 } else {
1395 for (size_t i=0; i<lenMixed; i++) {
1396 folded[i] = mapping[static_cast<unsigned char>(mixed[i])];
1397 }
1398 return lenMixed;
1399 }
1400}
1401
1402void CaseFolderTable::SetTranslation(char ch, char chTranslation) {
1403 mapping[static_cast<unsigned char>(ch)] = chTranslation;
1404}
1405
1406void CaseFolderTable::StandardASCII() {
1407 for (size_t iChar=0; iChar<sizeof(mapping); iChar++) {
1408 if (iChar >= 'A' && iChar <= 'Z') {
1409 mapping[iChar] = static_cast<char>(iChar - 'A' + 'a');
1410 } else {
1411 mapping[iChar] = static_cast<char>(iChar);
1412 }
1413 }
1414}
1415
1416bool Document::MatchesWordOptions(bool word, bool wordStart, int pos, int length) {
1417 return (!word && !wordStart) ||
1418 (word && IsWordAt(pos, pos + length)) ||
1419 (wordStart && IsWordStartAt(pos));
1420}
1421
65ec6247
RD
1422/**
1423 * Find text in document, supporting both forward and backward
1424 * searches (just pass minPos > maxPos to do a backward search)
1425 * Has not been tested with backwards DBCS searches yet.
1426 */
1dcf666d 1427long Document::FindText(int minPos, int maxPos, const char *search,
9e96e16f 1428 bool caseSensitive, bool word, bool wordStart, bool regExp, int flags,
1dcf666d
RD
1429 int *length, CaseFolder *pcf) {
1430 if (*length <= 0)
1431 return minPos;
65ec6247 1432 if (regExp) {
9e96e16f
RD
1433 if (!regex)
1434 regex = CreateRegexSearch(&charClass);
1dcf666d 1435 return regex->FindText(this, minPos, maxPos, search, caseSensitive, word, wordStart, flags, length);
65ec6247
RD
1436 } else {
1437
1dcf666d
RD
1438 const bool forward = minPos <= maxPos;
1439 const int increment = forward ? 1 : -1;
65ec6247
RD
1440
1441 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1dcf666d
RD
1442 const int startPos = MovePositionOutsideChar(minPos, increment, false);
1443 const int endPos = MovePositionOutsideChar(maxPos, increment, false);
65ec6247
RD
1444
1445 // Compute actual search ranges needed
1dcf666d
RD
1446 const int lengthFind = *length;
1447
65ec6247 1448 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
1dcf666d
RD
1449 const int limitPos = Platform::Maximum(startPos, endPos);
1450 int pos = startPos;
1451 if (!forward) {
1452 // Back all of a character
1453 pos = NextPosition(pos, increment);
1454 }
1455 if (caseSensitive) {
1456 const int endSearch = (startPos <= endPos) ? endPos - lengthFind + 1 : endPos;
1457 const char charStartSearch = search[0];
1458 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
1459 if (CharAt(pos) == charStartSearch) {
1460 bool found = (pos + lengthFind) <= limitPos;
1461 for (int indexSearch = 1; (indexSearch < lengthFind) && found; indexSearch++) {
1462 found = CharAt(pos + indexSearch) == search[indexSearch];
65ec6247 1463 }
1dcf666d
RD
1464 if (found && MatchesWordOptions(word, wordStart, pos, lengthFind)) {
1465 return pos;
65ec6247 1466 }
9ce192d4 1467 }
1dcf666d
RD
1468 if (!NextCharacter(pos, increment))
1469 break;
1470 }
1471 } else if (SC_CP_UTF8 == dbcsCodePage) {
1472 const size_t maxFoldingExpansion = 4;
1473 std::vector<char> searchThing(lengthFind * UTF8MaxBytes * maxFoldingExpansion + 1);
1474 const int lenSearch = static_cast<int>(
1475 pcf->Fold(&searchThing[0], searchThing.size(), search, lengthFind));
1476 char bytes[UTF8MaxBytes + 1];
1477 char folded[UTF8MaxBytes * maxFoldingExpansion + 1];
1478 while (forward ? (pos < endPos) : (pos >= endPos)) {
1479 int widthFirstCharacter = 0;
1480 int posIndexDocument = pos;
1481 int indexSearch = 0;
1482 bool characterMatches = true;
1483 for (;;) {
1484 const unsigned char leadByte = static_cast<unsigned char>(cb.CharAt(posIndexDocument));
1485 bytes[0] = leadByte;
1486 int widthChar = 1;
1487 if (!UTF8IsAscii(leadByte)) {
1488 const int widthCharBytes = UTF8BytesOfLead[leadByte];
1489 for (int b=1; b<widthCharBytes; b++) {
1490 bytes[b] = cb.CharAt(posIndexDocument+b);
1491 }
1492 widthChar = UTF8Classify(reinterpret_cast<const unsigned char *>(bytes), widthCharBytes) & UTF8MaskWidth;
65ec6247 1493 }
1dcf666d
RD
1494 if (!widthFirstCharacter)
1495 widthFirstCharacter = widthChar;
1496 if ((posIndexDocument + widthChar) > limitPos)
1497 break;
1498 const int lenFlat = static_cast<int>(pcf->Fold(folded, sizeof(folded), bytes, widthChar));
1499 folded[lenFlat] = 0;
1500 // Does folded match the buffer
1501 characterMatches = 0 == memcmp(folded, &searchThing[0] + indexSearch, lenFlat);
1502 if (!characterMatches)
1503 break;
1504 posIndexDocument += widthChar;
1505 indexSearch += lenFlat;
1506 if (indexSearch >= lenSearch)
1507 break;
1508 }
1509 if (characterMatches && (indexSearch == static_cast<int>(lenSearch))) {
1510 if (MatchesWordOptions(word, wordStart, pos, posIndexDocument - pos)) {
1511 *length = posIndexDocument - pos;
1512 return pos;
65ec6247 1513 }
9ce192d4 1514 }
1dcf666d
RD
1515 if (forward) {
1516 pos += widthFirstCharacter;
1517 } else {
1518 if (!NextCharacter(pos, increment))
1519 break;
1520 }
9ce192d4 1521 }
1dcf666d
RD
1522 } else if (dbcsCodePage) {
1523 const size_t maxBytesCharacter = 2;
1524 const size_t maxFoldingExpansion = 4;
1525 std::vector<char> searchThing(lengthFind * maxBytesCharacter * maxFoldingExpansion + 1);
1526 const int lenSearch = static_cast<int>(
1527 pcf->Fold(&searchThing[0], searchThing.size(), search, lengthFind));
1528 while (forward ? (pos < endPos) : (pos >= endPos)) {
1529 int indexDocument = 0;
1530 int indexSearch = 0;
1531 bool characterMatches = true;
1532 while (characterMatches &&
1533 ((pos + indexDocument) < limitPos) &&
1534 (indexSearch < lenSearch)) {
1535 char bytes[maxBytesCharacter + 1];
1536 bytes[0] = cb.CharAt(pos + indexDocument);
1537 const int widthChar = IsDBCSLeadByte(bytes[0]) ? 2 : 1;
1538 if (widthChar == 2)
1539 bytes[1] = cb.CharAt(pos + indexDocument + 1);
1540 if ((pos + indexDocument + widthChar) > limitPos)
1541 break;
1542 char folded[maxBytesCharacter * maxFoldingExpansion + 1];
1543 const int lenFlat = static_cast<int>(pcf->Fold(folded, sizeof(folded), bytes, widthChar));
1544 folded[lenFlat] = 0;
1545 // Does folded match the buffer
1546 characterMatches = 0 == memcmp(folded, &searchThing[0] + indexSearch, lenFlat);
1547 indexDocument += widthChar;
1548 indexSearch += lenFlat;
1549 }
1550 if (characterMatches && (indexSearch == static_cast<int>(lenSearch))) {
1551 if (MatchesWordOptions(word, wordStart, pos, indexDocument)) {
1552 *length = indexDocument;
1553 return pos;
1554 }
1555 }
1556 if (!NextCharacter(pos, increment))
1557 break;
1558 }
1559 } else {
1560 const int endSearch = (startPos <= endPos) ? endPos - lengthFind + 1 : endPos;
1561 std::vector<char> searchThing(lengthFind + 1);
1562 pcf->Fold(&searchThing[0], searchThing.size(), search, lengthFind);
1563 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
1564 bool found = (pos + lengthFind) <= limitPos;
1565 for (int indexSearch = 0; (indexSearch < lengthFind) && found; indexSearch++) {
1566 char ch = CharAt(pos + indexSearch);
1567 char folded[2];
1568 pcf->Fold(folded, sizeof(folded), &ch, 1);
1569 found = folded[0] == searchThing[indexSearch];
1570 }
1571 if (found && MatchesWordOptions(word, wordStart, pos, lengthFind)) {
1572 return pos;
1573 }
1574 if (!NextCharacter(pos, increment))
1575 break;
65ec6247 1576 }
9ce192d4
RD
1577 }
1578 }
1579 //Platform::DebugPrintf("Not found\n");
65ec6247
RD
1580 return -1;
1581}
1582
1583const char *Document::SubstituteByPosition(const char *text, int *length) {
1dcf666d
RD
1584 if (regex)
1585 return regex->SubstituteByPosition(this, text, length);
1586 else
1587 return 0;
9ce192d4
RD
1588}
1589
7e0c58e9 1590int Document::LinesTotal() const {
9ce192d4
RD
1591 return cb.Lines();
1592}
1593
f6bcfd97 1594void Document::ChangeCase(Range r, bool makeUpperCase) {
1e9bafca 1595 for (int pos = r.start; pos < r.end;) {
9e730a78 1596 int len = LenChar(pos);
1e9bafca 1597 if (len == 1) {
9e730a78 1598 char ch = CharAt(pos);
f6bcfd97 1599 if (makeUpperCase) {
9e730a78 1600 if (IsLowerCase(ch)) {
65ec6247 1601 ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
f6bcfd97
BP
1602 }
1603 } else {
9e730a78 1604 if (IsUpperCase(ch)) {
65ec6247 1605 ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
f6bcfd97
BP
1606 }
1607 }
1608 }
1e9bafca 1609 pos += len;
f6bcfd97
BP
1610 }
1611}
1612
591d01be 1613void Document::SetDefaultCharClasses(bool includeWordClass) {
b8193d80 1614 charClass.SetDefaultCharClasses(includeWordClass);
8e54aaed
RD
1615}
1616
b8193d80
RD
1617void Document::SetCharClasses(const unsigned char *chars, CharClassify::cc newCharClass) {
1618 charClass.SetCharClasses(chars, newCharClass);
9ce192d4
RD
1619}
1620
1dcf666d
RD
1621int Document::GetCharsOfClass(CharClassify::cc characterClass, unsigned char *buffer) {
1622 return charClass.GetCharsOfClass(characterClass, buffer);
1623}
1624
9ce192d4
RD
1625void Document::SetStylingBits(int bits) {
1626 stylingBits = bits;
7e0c58e9 1627 stylingBitsMask = (1 << stylingBits) - 1;
9ce192d4
RD
1628}
1629
1dcf666d 1630void SCI_METHOD Document::StartStyling(int position, char mask) {
9ce192d4 1631 stylingMask = mask;
65ec6247 1632 endStyled = position;
9ce192d4
RD
1633}
1634
1dcf666d 1635bool SCI_METHOD Document::SetStyleFor(int length, char style) {
7e0c58e9 1636 if (enteredStyling != 0) {
a834585d
RD
1637 return false;
1638 } else {
7e0c58e9 1639 enteredStyling++;
9e730a78 1640 style &= stylingMask;
9ce192d4 1641 int prevEndStyled = endStyled;
65ec6247
RD
1642 if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {
1643 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1644 prevEndStyled, length);
9ce192d4
RD
1645 NotifyModified(mh);
1646 }
65ec6247 1647 endStyled += length;
7e0c58e9 1648 enteredStyling--;
a834585d 1649 return true;
9ce192d4
RD
1650 }
1651}
1652
1dcf666d 1653bool SCI_METHOD Document::SetStyles(int length, const char *styles) {
7e0c58e9 1654 if (enteredStyling != 0) {
a834585d
RD
1655 return false;
1656 } else {
7e0c58e9 1657 enteredStyling++;
9ce192d4 1658 bool didChange = false;
1e9bafca
RD
1659 int startMod = 0;
1660 int endMod = 0;
65ec6247 1661 for (int iPos = 0; iPos < length; iPos++, endStyled++) {
1a2fb4cd 1662 PLATFORM_ASSERT(endStyled < Length());
65ec6247 1663 if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
1e9bafca
RD
1664 if (!didChange) {
1665 startMod = endStyled;
1666 }
9ce192d4 1667 didChange = true;
1e9bafca 1668 endMod = endStyled;
9ce192d4
RD
1669 }
1670 }
9ce192d4 1671 if (didChange) {
65ec6247 1672 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1e9bafca 1673 startMod, endMod - startMod + 1);
9ce192d4
RD
1674 NotifyModified(mh);
1675 }
7e0c58e9 1676 enteredStyling--;
a834585d 1677 return true;
9ce192d4
RD
1678 }
1679}
1680
7e0c58e9
RD
1681void Document::EnsureStyledTo(int pos) {
1682 if ((enteredStyling == 0) && (pos > GetEndStyled())) {
591d01be 1683 IncrementStyleClock();
1dcf666d
RD
1684 if (pli && !pli->UseContainerLexing()) {
1685 int lineEndStyled = LineFromPosition(GetEndStyled());
1686 int endStyledTo = LineStart(lineEndStyled);
1687 pli->Colourise(endStyledTo, pos);
1688 } else {
1689 // Ask the watchers to style, and stop as soon as one responds.
1690 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++) {
1691 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
1692 }
a834585d 1693 }
1a2fb4cd 1694 }
7e0c58e9
RD
1695}
1696
1dcf666d
RD
1697void Document::LexerChanged() {
1698 // Tell the watchers the lexer has changed.
1699 for (int i = 0; i < lenWatchers; i++) {
1700 watchers[i].watcher->NotifyLexerChanged(this, watchers[i].userData);
1701 }
1702}
1703
1704int SCI_METHOD Document::SetLineState(int line, int state) {
1705 int statePrevious = static_cast<LineState *>(perLineData[ldState])->SetLineState(line, state);
7e0c58e9 1706 if (state != statePrevious) {
1dcf666d 1707 DocModification mh(SC_MOD_CHANGELINESTATE, LineStart(line), 0, 0, 0, line);
7e0c58e9
RD
1708 NotifyModified(mh);
1709 }
1710 return statePrevious;
f6bcfd97
BP
1711}
1712
1dcf666d
RD
1713int SCI_METHOD Document::GetLineState(int line) const {
1714 return static_cast<LineState *>(perLineData[ldState])->GetLineState(line);
1715}
1716
1717int Document::GetMaxLineState() {
1718 return static_cast<LineState *>(perLineData[ldState])->GetMaxLineState();
9e96e16f
RD
1719}
1720
1dcf666d
RD
1721void SCI_METHOD Document::ChangeLexerState(int start, int end) {
1722 DocModification mh(SC_MOD_LEXERSTATE, start, end-start, 0, 0, 0);
1723 NotifyModified(mh);
9e96e16f
RD
1724}
1725
1726StyledText Document::MarginStyledText(int line) {
1dcf666d
RD
1727 LineAnnotation *pla = static_cast<LineAnnotation *>(perLineData[ldMargin]);
1728 return StyledText(pla->Length(line), pla->Text(line),
9e96e16f
RD
1729 pla->MultipleStyles(line), pla->Style(line), pla->Styles(line));
1730}
1731
1732void Document::MarginSetText(int line, const char *text) {
1dcf666d 1733 static_cast<LineAnnotation *>(perLineData[ldMargin])->SetText(line, text);
9e96e16f
RD
1734 DocModification mh(SC_MOD_CHANGEMARGIN, LineStart(line), 0, 0, 0, line);
1735 NotifyModified(mh);
1736}
1737
1738void Document::MarginSetStyle(int line, int style) {
1dcf666d
RD
1739 static_cast<LineAnnotation *>(perLineData[ldMargin])->SetStyle(line, style);
1740 NotifyModified(DocModification(SC_MOD_CHANGEMARGIN, LineStart(line), 0, 0, 0, line));
9e96e16f
RD
1741}
1742
1743void Document::MarginSetStyles(int line, const unsigned char *styles) {
1dcf666d
RD
1744 static_cast<LineAnnotation *>(perLineData[ldMargin])->SetStyles(line, styles);
1745 NotifyModified(DocModification(SC_MOD_CHANGEMARGIN, LineStart(line), 0, 0, 0, line));
9e96e16f
RD
1746}
1747
1748int Document::MarginLength(int line) const {
1dcf666d 1749 return static_cast<LineAnnotation *>(perLineData[ldMargin])->Length(line);
9e96e16f
RD
1750}
1751
1752void Document::MarginClearAll() {
1753 int maxEditorLine = LinesTotal();
1dcf666d 1754 for (int l=0; l<maxEditorLine; l++)
9e96e16f
RD
1755 MarginSetText(l, 0);
1756 // Free remaining data
1dcf666d 1757 static_cast<LineAnnotation *>(perLineData[ldMargin])->ClearAll();
9e96e16f
RD
1758}
1759
1760bool Document::AnnotationAny() const {
1dcf666d 1761 return static_cast<LineAnnotation *>(perLineData[ldAnnotation])->AnySet();
9e96e16f
RD
1762}
1763
1764StyledText Document::AnnotationStyledText(int line) {
1dcf666d
RD
1765 LineAnnotation *pla = static_cast<LineAnnotation *>(perLineData[ldAnnotation]);
1766 return StyledText(pla->Length(line), pla->Text(line),
9e96e16f
RD
1767 pla->MultipleStyles(line), pla->Style(line), pla->Styles(line));
1768}
1769
1770void Document::AnnotationSetText(int line, const char *text) {
1dcf666d
RD
1771 if (line >= 0 && line < LinesTotal()) {
1772 const int linesBefore = AnnotationLines(line);
1773 static_cast<LineAnnotation *>(perLineData[ldAnnotation])->SetText(line, text);
1774 const int linesAfter = AnnotationLines(line);
1775 DocModification mh(SC_MOD_CHANGEANNOTATION, LineStart(line), 0, 0, 0, line);
1776 mh.annotationLinesAdded = linesAfter - linesBefore;
1777 NotifyModified(mh);
1778 }
9e96e16f
RD
1779}
1780
1781void Document::AnnotationSetStyle(int line, int style) {
1dcf666d
RD
1782 static_cast<LineAnnotation *>(perLineData[ldAnnotation])->SetStyle(line, style);
1783 DocModification mh(SC_MOD_CHANGEANNOTATION, LineStart(line), 0, 0, 0, line);
1784 NotifyModified(mh);
9e96e16f
RD
1785}
1786
1787void Document::AnnotationSetStyles(int line, const unsigned char *styles) {
1dcf666d
RD
1788 if (line >= 0 && line < LinesTotal()) {
1789 static_cast<LineAnnotation *>(perLineData[ldAnnotation])->SetStyles(line, styles);
1790 }
9e96e16f
RD
1791}
1792
1793int Document::AnnotationLength(int line) const {
1dcf666d 1794 return static_cast<LineAnnotation *>(perLineData[ldAnnotation])->Length(line);
9e96e16f
RD
1795}
1796
1797int Document::AnnotationLines(int line) const {
1dcf666d 1798 return static_cast<LineAnnotation *>(perLineData[ldAnnotation])->Lines(line);
9e96e16f
RD
1799}
1800
1801void Document::AnnotationClearAll() {
1802 int maxEditorLine = LinesTotal();
1dcf666d 1803 for (int l=0; l<maxEditorLine; l++)
9e96e16f
RD
1804 AnnotationSetText(l, 0);
1805 // Free remaining data
1dcf666d 1806 static_cast<LineAnnotation *>(perLineData[ldAnnotation])->ClearAll();
9e96e16f
RD
1807}
1808
591d01be 1809void Document::IncrementStyleClock() {
7e0c58e9
RD
1810 styleClock = (styleClock + 1) % 0x100000;
1811}
1812
1dcf666d 1813void SCI_METHOD Document::DecorationFillRange(int position, int value, int fillLength) {
7e0c58e9
RD
1814 if (decorations.FillRange(position, value, fillLength)) {
1815 DocModification mh(SC_MOD_CHANGEINDICATOR | SC_PERFORMED_USER,
1816 position, fillLength);
1817 NotifyModified(mh);
591d01be
RD
1818 }
1819}
1820
9ce192d4
RD
1821bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
1822 for (int i = 0; i < lenWatchers; i++) {
1823 if ((watchers[i].watcher == watcher) &&
1824 (watchers[i].userData == userData))
1825 return false;
1826 }
1827 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
9ce192d4
RD
1828 for (int j = 0; j < lenWatchers; j++)
1829 pwNew[j] = watchers[j];
1830 pwNew[lenWatchers].watcher = watcher;
1831 pwNew[lenWatchers].userData = userData;
1832 delete []watchers;
1833 watchers = pwNew;
1834 lenWatchers++;
1835 return true;
1836}
1837
1838bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
1839 for (int i = 0; i < lenWatchers; i++) {
1840 if ((watchers[i].watcher == watcher) &&
1841 (watchers[i].userData == userData)) {
1842 if (lenWatchers == 1) {
1843 delete []watchers;
1844 watchers = 0;
1845 lenWatchers = 0;
1846 } else {
1847 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
9ce192d4
RD
1848 for (int j = 0; j < lenWatchers - 1; j++) {
1849 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
1850 }
1851 delete []watchers;
1852 watchers = pwNew;
1853 lenWatchers--;
1854 }
1855 return true;
1856 }
1857 }
1858 return false;
1859}
1860
1861void Document::NotifyModifyAttempt() {
1862 for (int i = 0; i < lenWatchers; i++) {
1863 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
1864 }
1865}
1866
1867void Document::NotifySavePoint(bool atSavePoint) {
1868 for (int i = 0; i < lenWatchers; i++) {
1869 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
1870 }
1871}
1872
1873void Document::NotifyModified(DocModification mh) {
7e0c58e9
RD
1874 if (mh.modificationType & SC_MOD_INSERTTEXT) {
1875 decorations.InsertSpace(mh.position, mh.length);
1876 } else if (mh.modificationType & SC_MOD_DELETETEXT) {
1877 decorations.DeleteRange(mh.position, mh.length);
1878 }
9ce192d4
RD
1879 for (int i = 0; i < lenWatchers; i++) {
1880 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
1881 }
1882}
65ec6247
RD
1883
1884bool Document::IsWordPartSeparator(char ch) {
b8193d80 1885 return (WordCharClass(ch) == CharClassify::ccWord) && IsPunctuation(ch);
65ec6247
RD
1886}
1887
1888int Document::WordPartLeft(int pos) {
1889 if (pos > 0) {
1890 --pos;
1891 char startChar = cb.CharAt(pos);
1892 if (IsWordPartSeparator(startChar)) {
1893 while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {
1894 --pos;
1895 }
1896 }
1897 if (pos > 0) {
1898 startChar = cb.CharAt(pos);
1899 --pos;
9e730a78
RD
1900 if (IsLowerCase(startChar)) {
1901 while (pos > 0 && IsLowerCase(cb.CharAt(pos)))
65ec6247 1902 --pos;
9e730a78 1903 if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos)))
65ec6247 1904 ++pos;
9e730a78
RD
1905 } else if (IsUpperCase(startChar)) {
1906 while (pos > 0 && IsUpperCase(cb.CharAt(pos)))
65ec6247 1907 --pos;
9e730a78 1908 if (!IsUpperCase(cb.CharAt(pos)))
65ec6247 1909 ++pos;
9e730a78
RD
1910 } else if (IsADigit(startChar)) {
1911 while (pos > 0 && IsADigit(cb.CharAt(pos)))
65ec6247 1912 --pos;
9e730a78 1913 if (!IsADigit(cb.CharAt(pos)))
65ec6247 1914 ++pos;
9e730a78
RD
1915 } else if (IsPunctuation(startChar)) {
1916 while (pos > 0 && IsPunctuation(cb.CharAt(pos)))
65ec6247 1917 --pos;
9e730a78 1918 if (!IsPunctuation(cb.CharAt(pos)))
65ec6247
RD
1919 ++pos;
1920 } else if (isspacechar(startChar)) {
1921 while (pos > 0 && isspacechar(cb.CharAt(pos)))
1922 --pos;
1923 if (!isspacechar(cb.CharAt(pos)))
1924 ++pos;
9e730a78
RD
1925 } else if (!isascii(startChar)) {
1926 while (pos > 0 && !isascii(cb.CharAt(pos)))
1927 --pos;
1928 if (isascii(cb.CharAt(pos)))
1929 ++pos;
1930 } else {
1931 ++pos;
65ec6247
RD
1932 }
1933 }
1934 }
1935 return pos;
1936}
1937
1938int Document::WordPartRight(int pos) {
1939 char startChar = cb.CharAt(pos);
1940 int length = Length();
1941 if (IsWordPartSeparator(startChar)) {
1942 while (pos < length && IsWordPartSeparator(cb.CharAt(pos)))
1943 ++pos;
1944 startChar = cb.CharAt(pos);
1945 }
9e730a78
RD
1946 if (!isascii(startChar)) {
1947 while (pos < length && !isascii(cb.CharAt(pos)))
65ec6247 1948 ++pos;
9e730a78
RD
1949 } else if (IsLowerCase(startChar)) {
1950 while (pos < length && IsLowerCase(cb.CharAt(pos)))
65ec6247 1951 ++pos;
9e730a78
RD
1952 } else if (IsUpperCase(startChar)) {
1953 if (IsLowerCase(cb.CharAt(pos + 1))) {
1954 ++pos;
1955 while (pos < length && IsLowerCase(cb.CharAt(pos)))
65ec6247
RD
1956 ++pos;
1957 } else {
9e730a78 1958 while (pos < length && IsUpperCase(cb.CharAt(pos)))
65ec6247
RD
1959 ++pos;
1960 }
9e730a78 1961 if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1)))
65ec6247 1962 --pos;
9e730a78
RD
1963 } else if (IsADigit(startChar)) {
1964 while (pos < length && IsADigit(cb.CharAt(pos)))
65ec6247 1965 ++pos;
9e730a78
RD
1966 } else if (IsPunctuation(startChar)) {
1967 while (pos < length && IsPunctuation(cb.CharAt(pos)))
65ec6247
RD
1968 ++pos;
1969 } else if (isspacechar(startChar)) {
1970 while (pos < length && isspacechar(cb.CharAt(pos)))
1971 ++pos;
9e730a78
RD
1972 } else {
1973 ++pos;
1974 }
1975 return pos;
1976}
1977
8e54aaed
RD
1978bool IsLineEndChar(char c) {
1979 return (c == '\n' || c == '\r');
1980}
1981
1982int Document::ExtendStyleRange(int pos, int delta, bool singleLine) {
9e730a78
RD
1983 int sStart = cb.StyleAt(pos);
1984 if (delta < 0) {
1dcf666d 1985 while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))))
9e730a78
RD
1986 pos--;
1987 pos++;
1988 } else {
1dcf666d 1989 while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))))
9e730a78 1990 pos++;
65ec6247
RD
1991 }
1992 return pos;
1993}
1e9bafca
RD
1994
1995static char BraceOpposite(char ch) {
1996 switch (ch) {
1997 case '(':
1998 return ')';
1999 case ')':
2000 return '(';
2001 case '[':
2002 return ']';
2003 case ']':
2004 return '[';
2005 case '{':
2006 return '}';
2007 case '}':
2008 return '{';
2009 case '<':
2010 return '>';
2011 case '>':
2012 return '<';
2013 default:
2014 return '\0';
2015 }
2016}
2017
2018// TODO: should be able to extend styled region to find matching brace
2019int Document::BraceMatch(int position, int /*maxReStyle*/) {
2020 char chBrace = CharAt(position);
2021 char chSeek = BraceOpposite(chBrace);
2022 if (chSeek == '\0')
2023 return - 1;
2024 char styBrace = static_cast<char>(StyleAt(position) & stylingBitsMask);
2025 int direction = -1;
2026 if (chBrace == '(' || chBrace == '[' || chBrace == '{' || chBrace == '<')
2027 direction = 1;
2028 int depth = 1;
1dcf666d 2029 position = NextPosition(position, direction);
1e9bafca 2030 while ((position >= 0) && (position < Length())) {
1e9bafca
RD
2031 char chAtPos = CharAt(position);
2032 char styAtPos = static_cast<char>(StyleAt(position) & stylingBitsMask);
2033 if ((position > GetEndStyled()) || (styAtPos == styBrace)) {
2034 if (chAtPos == chBrace)
2035 depth++;
2036 if (chAtPos == chSeek)
2037 depth--;
2038 if (depth == 0)
2039 return position;
2040 }
1dcf666d
RD
2041 int positionBeforeMove = position;
2042 position = NextPosition(position, direction);
2043 if (position == positionBeforeMove)
2044 break;
1e9bafca
RD
2045 }
2046 return - 1;
2047}
9e96e16f
RD
2048
2049/**
2050 * Implementation of RegexSearchBase for the default built-in regular expression engine
2051 */
2052class BuiltinRegex : public RegexSearchBase {
2053public:
2054 BuiltinRegex(CharClassify *charClassTable) : search(charClassTable), substituted(NULL) {}
2055
2056 virtual ~BuiltinRegex() {
2057 delete substituted;
2058 }
2059
2060 virtual long FindText(Document *doc, int minPos, int maxPos, const char *s,
2061 bool caseSensitive, bool word, bool wordStart, int flags,
2062 int *length);
2063
1dcf666d 2064 virtual const char *SubstituteByPosition(Document *doc, const char *text, int *length);
9e96e16f
RD
2065
2066private:
2067 RESearch search;
2068 char *substituted;
2069};
2070
2071// Define a way for the Regular Expression code to access the document
2072class DocumentIndexer : public CharacterIndexer {
2073 Document *pdoc;
2074 int end;
2075public:
2076 DocumentIndexer(Document *pdoc_, int end_) :
2077 pdoc(pdoc_), end(end_) {
2078 }
2079
2080 virtual ~DocumentIndexer() {
2081 }
2082
2083 virtual char CharAt(int index) {
2084 if (index < 0 || index >= end)
2085 return 0;
2086 else
2087 return pdoc->CharAt(index);
2088 }
2089};
2090
2091long BuiltinRegex::FindText(Document *doc, int minPos, int maxPos, const char *s,
2092 bool caseSensitive, bool, bool, int flags,
2093 int *length) {
2094 bool posix = (flags & SCFIND_POSIX) != 0;
2095 int increment = (minPos <= maxPos) ? 1 : -1;
2096
2097 int startPos = minPos;
2098 int endPos = maxPos;
2099
2100 // Range endpoints should not be inside DBCS characters, but just in case, move them.
2101 startPos = doc->MovePositionOutsideChar(startPos, 1, false);
2102 endPos = doc->MovePositionOutsideChar(endPos, 1, false);
2103
2104 const char *errmsg = search.Compile(s, *length, caseSensitive, posix);
2105 if (errmsg) {
2106 return -1;
2107 }
2108 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
2109 // Replace first '.' with '-' in each property file variable reference:
2110 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
2111 // Replace: $(\1-\2)
2112 int lineRangeStart = doc->LineFromPosition(startPos);
2113 int lineRangeEnd = doc->LineFromPosition(endPos);
2114 if ((increment == 1) &&
2115 (startPos >= doc->LineEnd(lineRangeStart)) &&
2116 (lineRangeStart < lineRangeEnd)) {
2117 // the start position is at end of line or between line end characters.
2118 lineRangeStart++;
2119 startPos = doc->LineStart(lineRangeStart);
1dcf666d
RD
2120 } else if ((increment == -1) &&
2121 (startPos <= doc->LineStart(lineRangeStart)) &&
2122 (lineRangeStart > lineRangeEnd)) {
2123 // the start position is at beginning of line.
2124 lineRangeStart--;
2125 startPos = doc->LineEnd(lineRangeStart);
9e96e16f
RD
2126 }
2127 int pos = -1;
2128 int lenRet = 0;
2129 char searchEnd = s[*length - 1];
1dcf666d 2130 char searchEndPrev = (*length > 1) ? s[*length - 2] : '\0';
9e96e16f
RD
2131 int lineRangeBreak = lineRangeEnd + increment;
2132 for (int line = lineRangeStart; line != lineRangeBreak; line += increment) {
2133 int startOfLine = doc->LineStart(line);
2134 int endOfLine = doc->LineEnd(line);
2135 if (increment == 1) {
2136 if (line == lineRangeStart) {
2137 if ((startPos != startOfLine) && (s[0] == '^'))
2138 continue; // Can't match start of line if start position after start of line
2139 startOfLine = startPos;
2140 }
2141 if (line == lineRangeEnd) {
1dcf666d 2142 if ((endPos != endOfLine) && (searchEnd == '$') && (searchEndPrev != '\\'))
9e96e16f
RD
2143 continue; // Can't match end of line if end position before end of line
2144 endOfLine = endPos;
2145 }
2146 } else {
2147 if (line == lineRangeEnd) {
2148 if ((endPos != startOfLine) && (s[0] == '^'))
2149 continue; // Can't match start of line if end position after start of line
2150 startOfLine = endPos;
2151 }
2152 if (line == lineRangeStart) {
1dcf666d 2153 if ((startPos != endOfLine) && (searchEnd == '$') && (searchEndPrev != '\\'))
9e96e16f
RD
2154 continue; // Can't match end of line if start position before end of line
2155 endOfLine = startPos;
2156 }
2157 }
2158
2159 DocumentIndexer di(doc, endOfLine);
2160 int success = search.Execute(di, startOfLine, endOfLine);
2161 if (success) {
2162 pos = search.bopat[0];
2163 lenRet = search.eopat[0] - search.bopat[0];
1dcf666d
RD
2164 // There can be only one start of a line, so no need to look for last match in line
2165 if ((increment == -1) && (s[0] != '^')) {
9e96e16f
RD
2166 // Check for the last match on this line.
2167 int repetitions = 1000; // Break out of infinite loop
2168 while (success && (search.eopat[0] <= endOfLine) && (repetitions--)) {
2169 success = search.Execute(di, pos+1, endOfLine);
2170 if (success) {
2171 if (search.eopat[0] <= minPos) {
2172 pos = search.bopat[0];
2173 lenRet = search.eopat[0] - search.bopat[0];
2174 } else {
2175 success = 0;
2176 }
2177 }
2178 }
2179 }
2180 break;
2181 }
2182 }
2183 *length = lenRet;
2184 return pos;
2185}
2186
1dcf666d 2187const char *BuiltinRegex::SubstituteByPosition(Document *doc, const char *text, int *length) {
9e96e16f
RD
2188 delete []substituted;
2189 substituted = 0;
2190 DocumentIndexer di(doc, doc->Length());
2191 if (!search.GrabMatches(di))
2192 return 0;
2193 unsigned int lenResult = 0;
2194 for (int i = 0; i < *length; i++) {
2195 if (text[i] == '\\') {
1dcf666d 2196 if (text[i + 1] >= '0' && text[i + 1] <= '9') {
9e96e16f
RD
2197 unsigned int patNum = text[i + 1] - '0';
2198 lenResult += search.eopat[patNum] - search.bopat[patNum];
2199 i++;
2200 } else {
2201 switch (text[i + 1]) {
2202 case 'a':
2203 case 'b':
2204 case 'f':
2205 case 'n':
2206 case 'r':
2207 case 't':
2208 case 'v':
1dcf666d 2209 case '\\':
9e96e16f
RD
2210 i++;
2211 }
2212 lenResult++;
2213 }
2214 } else {
2215 lenResult++;
2216 }
2217 }
2218 substituted = new char[lenResult + 1];
2219 char *o = substituted;
2220 for (int j = 0; j < *length; j++) {
2221 if (text[j] == '\\') {
1dcf666d 2222 if (text[j + 1] >= '0' && text[j + 1] <= '9') {
9e96e16f
RD
2223 unsigned int patNum = text[j + 1] - '0';
2224 unsigned int len = search.eopat[patNum] - search.bopat[patNum];
2225 if (search.pat[patNum]) // Will be null if try for a match that did not occur
2226 memcpy(o, search.pat[patNum], len);
2227 o += len;
2228 j++;
2229 } else {
2230 j++;
2231 switch (text[j]) {
2232 case 'a':
2233 *o++ = '\a';
2234 break;
2235 case 'b':
2236 *o++ = '\b';
2237 break;
2238 case 'f':
2239 *o++ = '\f';
2240 break;
2241 case 'n':
2242 *o++ = '\n';
2243 break;
2244 case 'r':
2245 *o++ = '\r';
2246 break;
2247 case 't':
2248 *o++ = '\t';
2249 break;
2250 case 'v':
2251 *o++ = '\v';
2252 break;
1dcf666d
RD
2253 case '\\':
2254 *o++ = '\\';
2255 break;
9e96e16f
RD
2256 default:
2257 *o++ = '\\';
2258 j--;
2259 }
2260 }
2261 } else {
2262 *o++ = text[j];
2263 }
2264 }
2265 *o = '\0';
2266 *length = lenResult;
2267 return substituted;
2268}
2269
2270#ifndef SCI_OWNREGEX
2271
2272#ifdef SCI_NAMESPACE
2273
2274RegexSearchBase *Scintilla::CreateRegexSearch(CharClassify *charClassTable) {
2275 return new BuiltinRegex(charClassTable);
2276}
2277
2278#else
2279
2280RegexSearchBase *CreateRegexSearch(CharClassify *charClassTable) {
2281 return new BuiltinRegex(charClassTable);
2282}
2283
2284#endif
2285
2286#endif