]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
Test commit
[wxWidgets.git] / src / stc / scintilla / src / Document.cxx
1 // Scintilla source code edit control
2 /** @file Document.cxx
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
4 **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
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>
12
13 #include "Platform.h"
14
15 #include "Scintilla.h"
16 #include "SVector.h"
17 #include "CellBuffer.h"
18 #include "Document.h"
19 #include "RESearch.h"
20
21 // This is ASCII specific but is safe with chars >= 0x80
22 static inline bool isspacechar(unsigned char ch) {
23 return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
24 }
25
26 static inline bool IsPunctuation(char ch) {
27 return isascii(ch) && ispunct(ch);
28 }
29
30 static inline bool IsADigit(char ch) {
31 return isascii(ch) && isdigit(ch);
32 }
33
34 static inline bool IsLowerCase(char ch) {
35 return isascii(ch) && islower(ch);
36 }
37
38 static inline bool IsUpperCase(char ch) {
39 return isascii(ch) && isupper(ch);
40 }
41
42 Document::Document() {
43 refCount = 0;
44 #ifdef unix
45 eolMode = SC_EOL_LF;
46 #else
47 eolMode = SC_EOL_CRLF;
48 #endif
49 dbcsCodePage = 0;
50 stylingBits = 5;
51 stylingBitsMask = 0x1F;
52 stylingMask = 0;
53 SetDefaultCharClasses(true);
54 endStyled = 0;
55 styleClock = 0;
56 enteredCount = 0;
57 enteredReadOnlyCount = 0;
58 tabInChars = 8;
59 indentInChars = 0;
60 actualIndentInChars = 8;
61 useTabs = true;
62 tabIndents = true;
63 backspaceUnindents = false;
64 watchers = 0;
65 lenWatchers = 0;
66
67 matchesValid = false;
68 pre = 0;
69 substituted = 0;
70 }
71
72 Document::~Document() {
73 for (int i = 0; i < lenWatchers; i++) {
74 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
75 }
76 delete []watchers;
77 watchers = 0;
78 lenWatchers = 0;
79 delete pre;
80 pre = 0;
81 delete []substituted;
82 substituted = 0;
83 }
84
85 // Increase reference count and return its previous value.
86 int Document::AddRef() {
87 return refCount++;
88 }
89
90 // Decrease reference count and return its previous value.
91 // Delete the document if reference count reaches zero.
92 int Document::Release() {
93 int curRefCount = --refCount;
94 if (curRefCount == 0)
95 delete this;
96 return curRefCount;
97 }
98
99 void Document::SetSavePoint() {
100 cb.SetSavePoint();
101 NotifySavePoint(true);
102 }
103
104 int Document::AddMark(int line, int markerNum) {
105 int prev = cb.AddMark(line, markerNum);
106 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
107 NotifyModified(mh);
108 return prev;
109 }
110
111 void Document::DeleteMark(int line, int markerNum) {
112 cb.DeleteMark(line, markerNum);
113 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
114 NotifyModified(mh);
115 }
116
117 void Document::DeleteMarkFromHandle(int markerHandle) {
118 cb.DeleteMarkFromHandle(markerHandle);
119 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
120 NotifyModified(mh);
121 }
122
123 void Document::DeleteAllMarks(int markerNum) {
124 cb.DeleteAllMarks(markerNum);
125 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
126 NotifyModified(mh);
127 }
128
129 int Document::LineStart(int line) {
130 return cb.LineStart(line);
131 }
132
133 int Document::LineEnd(int line) {
134 if (line == LinesTotal() - 1) {
135 return LineStart(line + 1);
136 } else {
137 int position = LineStart(line + 1) - 1;
138 // When line terminator is CR+LF, may need to go back one more
139 if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
140 position--;
141 }
142 return position;
143 }
144 }
145
146 int Document::LineFromPosition(int pos) {
147 return cb.LineFromPosition(pos);
148 }
149
150 int Document::LineEndPosition(int position) {
151 return LineEnd(LineFromPosition(position));
152 }
153
154 int Document::VCHomePosition(int position) {
155 int line = LineFromPosition(position);
156 int startPosition = LineStart(line);
157 int endLine = LineStart(line + 1) - 1;
158 int startText = startPosition;
159 while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
160 startText++;
161 if (position == startText)
162 return startPosition;
163 else
164 return startText;
165 }
166
167 int Document::SetLevel(int line, int level) {
168 int prev = cb.SetLevel(line, level);
169 if (prev != level) {
170 DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
171 LineStart(line), 0, 0, 0);
172 mh.line = line;
173 mh.foldLevelNow = level;
174 mh.foldLevelPrev = prev;
175 NotifyModified(mh);
176 }
177 return prev;
178 }
179
180 static bool IsSubordinate(int levelStart, int levelTry) {
181 if (levelTry & SC_FOLDLEVELWHITEFLAG)
182 return true;
183 else
184 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
185 }
186
187 int Document::GetLastChild(int lineParent, int level) {
188 if (level == -1)
189 level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
190 int maxLine = LinesTotal();
191 int lineMaxSubord = lineParent;
192 while (lineMaxSubord < maxLine - 1) {
193 EnsureStyledTo(LineStart(lineMaxSubord + 2));
194 if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))
195 break;
196 lineMaxSubord++;
197 }
198 if (lineMaxSubord > lineParent) {
199 if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) {
200 // Have chewed up some whitespace that belongs to a parent so seek back
201 if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) {
202 lineMaxSubord--;
203 }
204 }
205 }
206 return lineMaxSubord;
207 }
208
209 int Document::GetFoldParent(int line) {
210 int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK;
211 int lineLook = line - 1;
212 while ((lineLook > 0) && (
213 (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
214 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
215 ) {
216 lineLook--;
217 }
218 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
219 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
220 return lineLook;
221 } else {
222 return -1;
223 }
224 }
225
226 int Document::ClampPositionIntoDocument(int pos) {
227 return Platform::Clamp(pos, 0, Length());
228 }
229
230 bool Document::IsCrLf(int pos) {
231 if (pos < 0)
232 return false;
233 if (pos >= (Length() - 1))
234 return false;
235 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
236 }
237
238 static const int maxBytesInDBCSCharacter=5;
239
240 int Document::LenChar(int pos) {
241 if (pos < 0) {
242 return 1;
243 } else if (IsCrLf(pos)) {
244 return 2;
245 } else if (SC_CP_UTF8 == dbcsCodePage) {
246 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
247 if (ch < 0x80)
248 return 1;
249 int len = 2;
250 if (ch >= (0x80 + 0x40 + 0x20))
251 len = 3;
252 int lengthDoc = Length();
253 if ((pos + len) > lengthDoc)
254 return lengthDoc -pos;
255 else
256 return len;
257 } else if (dbcsCodePage) {
258 char mbstr[maxBytesInDBCSCharacter+1];
259 int i;
260 for (i=0; i<Platform::DBCSCharMaxLength(); i++) {
261 mbstr[i] = cb.CharAt(pos+i);
262 }
263 mbstr[i] = '\0';
264 return Platform::DBCSCharLength(dbcsCodePage, mbstr);
265 } else {
266 return 1;
267 }
268 }
269 #include <assert.h>
270 // Normalise a position so that it is not halfway through a two byte character.
271 // This can occur in two situations -
272 // When lines are terminated with \r\n pairs which should be treated as one character.
273 // When displaying DBCS text such as Japanese.
274 // If moving, move the position in the indicated direction.
275 int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
276 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
277 // If out of range, just return minimum/maximum value.
278 if (pos <= 0)
279 return 0;
280 if (pos >= Length())
281 return Length();
282
283 // assert pos > 0 && pos < Length()
284 if (checkLineEnd && IsCrLf(pos - 1)) {
285 if (moveDir > 0)
286 return pos + 1;
287 else
288 return pos - 1;
289 }
290
291 // Not between CR and LF
292
293 if (dbcsCodePage) {
294 if (SC_CP_UTF8 == dbcsCodePage) {
295 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
296 while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
297 // ch is a trail byte
298 if (moveDir > 0)
299 pos++;
300 else
301 pos--;
302 ch = static_cast<unsigned char>(cb.CharAt(pos));
303 }
304 } else {
305 // Anchor DBCS calculations at start of line because start of line can
306 // not be a DBCS trail byte.
307 int posCheck = LineStart(LineFromPosition(pos));
308 while (posCheck < pos) {
309 char mbstr[maxBytesInDBCSCharacter+1];
310 int i;
311 for(i=0;i<Platform::DBCSCharMaxLength();i++) {
312 mbstr[i] = cb.CharAt(posCheck+i);
313 }
314 mbstr[i] = '\0';
315
316 int mbsize = Platform::DBCSCharLength(dbcsCodePage, mbstr);
317 if (posCheck + mbsize == pos) {
318 return pos;
319 } else if (posCheck + mbsize > pos) {
320 if (moveDir > 0) {
321 return posCheck + mbsize;
322 } else {
323 return posCheck;
324 }
325 }
326 posCheck += mbsize;
327 }
328 }
329 }
330
331 return pos;
332 }
333
334 void Document::ModifiedAt(int pos) {
335 if (endStyled > pos)
336 endStyled = pos;
337 }
338
339 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
340 // SetStyleAt does not change the persistent state of a document
341
342 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
343 bool Document::DeleteChars(int pos, int len) {
344 if (len == 0)
345 return false;
346 if ((pos + len) > Length())
347 return false;
348 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
349 enteredReadOnlyCount++;
350 NotifyModifyAttempt();
351 enteredReadOnlyCount--;
352 }
353 if (enteredCount != 0) {
354 return false;
355 } else {
356 enteredCount++;
357 if (!cb.IsReadOnly()) {
358 NotifyModified(
359 DocModification(
360 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
361 pos, len,
362 0, 0));
363 int prevLinesTotal = LinesTotal();
364 bool startSavePoint = cb.IsSavePoint();
365 const char *text = cb.DeleteChars(pos * 2, len * 2);
366 if (startSavePoint && cb.IsCollectingUndo())
367 NotifySavePoint(!startSavePoint);
368 if ((pos < Length()) || (pos == 0))
369 ModifiedAt(pos);
370 else
371 ModifiedAt(pos-1);
372 NotifyModified(
373 DocModification(
374 SC_MOD_DELETETEXT | SC_PERFORMED_USER,
375 pos, len,
376 LinesTotal() - prevLinesTotal, text));
377 }
378 enteredCount--;
379 }
380 return !cb.IsReadOnly();
381 }
382
383 /**
384 * Insert a styled string (char/style pairs) with a length.
385 */
386 bool Document::InsertStyledString(int position, char *s, int insertLength) {
387 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
388 enteredReadOnlyCount++;
389 NotifyModifyAttempt();
390 enteredReadOnlyCount--;
391 }
392 if (enteredCount != 0) {
393 return false;
394 } else {
395 enteredCount++;
396 if (!cb.IsReadOnly()) {
397 NotifyModified(
398 DocModification(
399 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
400 position / 2, insertLength / 2,
401 0, s));
402 int prevLinesTotal = LinesTotal();
403 bool startSavePoint = cb.IsSavePoint();
404 const char *text = cb.InsertString(position, s, insertLength);
405 if (startSavePoint && cb.IsCollectingUndo())
406 NotifySavePoint(!startSavePoint);
407 ModifiedAt(position / 2);
408 NotifyModified(
409 DocModification(
410 SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
411 position / 2, insertLength / 2,
412 LinesTotal() - prevLinesTotal, text));
413 }
414 enteredCount--;
415 }
416 return !cb.IsReadOnly();
417 }
418
419 int Document::Undo() {
420 int newPos = 0;
421 if (enteredCount == 0) {
422 enteredCount++;
423 bool startSavePoint = cb.IsSavePoint();
424 int steps = cb.StartUndo();
425 //Platform::DebugPrintf("Steps=%d\n", steps);
426 for (int step = 0; step < steps; step++) {
427 int prevLinesTotal = LinesTotal();
428 const Action &action = cb.GetUndoStep();
429 if (action.at == removeAction) {
430 NotifyModified(DocModification(
431 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
432 } else {
433 NotifyModified(DocModification(
434 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
435 }
436 cb.PerformUndoStep();
437 int cellPosition = action.position / 2;
438 ModifiedAt(cellPosition);
439 newPos = cellPosition;
440
441 int modFlags = SC_PERFORMED_UNDO;
442 // With undo, an insertion action becomes a deletion notification
443 if (action.at == removeAction) {
444 newPos += action.lenData;
445 modFlags |= SC_MOD_INSERTTEXT;
446 } else {
447 modFlags |= SC_MOD_DELETETEXT;
448 }
449 if (step == steps - 1)
450 modFlags |= SC_LASTSTEPINUNDOREDO;
451 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
452 LinesTotal() - prevLinesTotal, action.data));
453 }
454
455 bool endSavePoint = cb.IsSavePoint();
456 if (startSavePoint != endSavePoint)
457 NotifySavePoint(endSavePoint);
458 enteredCount--;
459 }
460 return newPos;
461 }
462
463 int Document::Redo() {
464 int newPos = 0;
465 if (enteredCount == 0) {
466 enteredCount++;
467 bool startSavePoint = cb.IsSavePoint();
468 int steps = cb.StartRedo();
469 for (int step = 0; step < steps; step++) {
470 int prevLinesTotal = LinesTotal();
471 const Action &action = cb.GetRedoStep();
472 if (action.at == insertAction) {
473 NotifyModified(DocModification(
474 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
475 } else {
476 NotifyModified(DocModification(
477 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
478 }
479 cb.PerformRedoStep();
480 ModifiedAt(action.position / 2);
481 newPos = action.position / 2;
482
483 int modFlags = SC_PERFORMED_REDO;
484 if (action.at == insertAction) {
485 newPos += action.lenData;
486 modFlags |= SC_MOD_INSERTTEXT;
487 } else {
488 modFlags |= SC_MOD_DELETETEXT;
489 }
490 if (step == steps - 1)
491 modFlags |= SC_LASTSTEPINUNDOREDO;
492 NotifyModified(
493 DocModification(modFlags, action.position / 2, action.lenData,
494 LinesTotal() - prevLinesTotal, action.data));
495 }
496
497 bool endSavePoint = cb.IsSavePoint();
498 if (startSavePoint != endSavePoint)
499 NotifySavePoint(endSavePoint);
500 enteredCount--;
501 }
502 return newPos;
503 }
504
505 /**
506 * Insert a single character.
507 */
508 bool Document::InsertChar(int pos, char ch) {
509 char chs[2];
510 chs[0] = ch;
511 chs[1] = 0;
512 return InsertStyledString(pos*2, chs, 2);
513 }
514
515 /**
516 * Insert a null terminated string.
517 */
518 bool Document::InsertString(int position, const char *s) {
519 return InsertString(position, s, strlen(s));
520 }
521
522 /**
523 * Insert a string with a length.
524 */
525 bool Document::InsertString(int position, const char *s, size_t insertLength) {
526 bool changed = false;
527 char *sWithStyle = new char[insertLength * 2];
528 if (sWithStyle) {
529 for (size_t i = 0; i < insertLength; i++) {
530 sWithStyle[i*2] = s[i];
531 sWithStyle[i*2 + 1] = 0;
532 }
533 changed = InsertStyledString(position*2, sWithStyle,
534 static_cast<int>(insertLength*2));
535 delete []sWithStyle;
536 }
537 return changed;
538 }
539
540 void Document::ChangeChar(int pos, char ch) {
541 DeleteChars(pos, 1);
542 InsertChar(pos, ch);
543 }
544
545 void Document::DelChar(int pos) {
546 DeleteChars(pos, LenChar(pos));
547 }
548
549 void Document::DelCharBack(int pos) {
550 if (pos <= 0) {
551 return;
552 } else if (IsCrLf(pos - 2)) {
553 DeleteChars(pos - 2, 2);
554 } else if (dbcsCodePage) {
555 int startChar = MovePositionOutsideChar(pos - 1, -1, false);
556 DeleteChars(startChar, pos - startChar);
557 } else {
558 DeleteChars(pos - 1, 1);
559 }
560 }
561
562 static bool isindentchar(char ch) {
563 return (ch == ' ') || (ch == '\t');
564 }
565
566 static int NextTab(int pos, int tabSize) {
567 return ((pos / tabSize) + 1) * tabSize;
568 }
569
570 static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
571 length--; // ensure space for \0
572 if (!insertSpaces) {
573 while ((indent >= tabSize) && (length > 0)) {
574 *linebuf++ = '\t';
575 indent -= tabSize;
576 length--;
577 }
578 }
579 while ((indent > 0) && (length > 0)) {
580 *linebuf++ = ' ';
581 indent--;
582 length--;
583 }
584 *linebuf = '\0';
585 }
586
587 int Document::GetLineIndentation(int line) {
588 int indent = 0;
589 if ((line >= 0) && (line < LinesTotal())) {
590 int lineStart = LineStart(line);
591 int length = Length();
592 for (int i = lineStart;i < length;i++) {
593 char ch = cb.CharAt(i);
594 if (ch == ' ')
595 indent++;
596 else if (ch == '\t')
597 indent = NextTab(indent, tabInChars);
598 else
599 return indent;
600 }
601 }
602 return indent;
603 }
604
605 void Document::SetLineIndentation(int line, int indent) {
606 int indentOfLine = GetLineIndentation(line);
607 if (indent < 0)
608 indent = 0;
609 if (indent != indentOfLine) {
610 char linebuf[1000];
611 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
612 int thisLineStart = LineStart(line);
613 int indentPos = GetLineIndentPosition(line);
614 DeleteChars(thisLineStart, indentPos - thisLineStart);
615 InsertString(thisLineStart, linebuf);
616 }
617 }
618
619 int Document::GetLineIndentPosition(int line) {
620 if (line < 0)
621 return 0;
622 int pos = LineStart(line);
623 int length = Length();
624 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
625 pos++;
626 }
627 return pos;
628 }
629
630 int Document::GetColumn(int pos) {
631 int column = 0;
632 int line = LineFromPosition(pos);
633 if ((line >= 0) && (line < LinesTotal())) {
634 for (int i = LineStart(line);i < pos;) {
635 char ch = cb.CharAt(i);
636 if (ch == '\t') {
637 column = NextTab(column, tabInChars);
638 i++;
639 } else if (ch == '\r') {
640 return column;
641 } else if (ch == '\n') {
642 return column;
643 } else {
644 column++;
645 i = MovePositionOutsideChar(i + 1, 1);
646 }
647 }
648 }
649 return column;
650 }
651
652 int Document::FindColumn(int line, int column) {
653 int position = LineStart(line);
654 int columnCurrent = 0;
655 if ((line >= 0) && (line < LinesTotal())) {
656 while ((columnCurrent < column) && (position < Length())) {
657 char ch = cb.CharAt(position);
658 if (ch == '\t') {
659 columnCurrent = NextTab(columnCurrent, tabInChars);
660 position++;
661 } else if (ch == '\r') {
662 return position;
663 } else if (ch == '\n') {
664 return position;
665 } else {
666 columnCurrent++;
667 position = MovePositionOutsideChar(position + 1, 1);
668 }
669 }
670 }
671 return position;
672 }
673
674 void Document::Indent(bool forwards, int lineBottom, int lineTop) {
675 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
676 for (int line = lineBottom; line >= lineTop; line--) {
677 int indentOfLine = GetLineIndentation(line);
678 if (forwards)
679 SetLineIndentation(line, indentOfLine + IndentSize());
680 else
681 SetLineIndentation(line, indentOfLine - IndentSize());
682 }
683 }
684
685 // Convert line endings for a piece of text to a particular mode.
686 // Stop at len or when a NUL is found.
687 // Caller must delete the returned pointer.
688 char *Document::TransformLineEnds(int *pLenOut, const char *s, size_t len, int eolMode) {
689 char *dest = new char[2 * len + 1];
690 const char *sptr = s;
691 char *dptr = dest;
692 for (size_t i = 0; (i < len) && (*sptr != '\0'); i++) {
693 if (*sptr == '\n' || *sptr == '\r') {
694 if (eolMode == SC_EOL_CR) {
695 *dptr++ = '\r';
696 } else if (eolMode == SC_EOL_LF) {
697 *dptr++ = '\n';
698 } else { // eolMode == SC_EOL_CRLF
699 *dptr++ = '\r';
700 *dptr++ = '\n';
701 }
702 if ((*sptr == '\r') && (i+1 < len) && (*(sptr+1) == '\n')) {
703 i++;
704 sptr++;
705 }
706 sptr++;
707 } else {
708 *dptr++ = *sptr++;
709 }
710 }
711 *dptr++ = '\0';
712 *pLenOut = (dptr - dest) - 1;
713 return dest;
714 }
715
716 void Document::ConvertLineEnds(int eolModeSet) {
717 BeginUndoAction();
718
719 for (int pos = 0; pos < Length(); pos++) {
720 if (cb.CharAt(pos) == '\r') {
721 if (cb.CharAt(pos + 1) == '\n') {
722 // CRLF
723 if (eolModeSet == SC_EOL_CR) {
724 DeleteChars(pos + 1, 1); // Delete the LF
725 } else if (eolModeSet == SC_EOL_LF) {
726 DeleteChars(pos, 1); // Delete the CR
727 } else {
728 pos++;
729 }
730 } else {
731 // CR
732 if (eolModeSet == SC_EOL_CRLF) {
733 InsertString(pos + 1, "\n", 1); // Insert LF
734 pos++;
735 } else if (eolModeSet == SC_EOL_LF) {
736 InsertString(pos, "\n", 1); // Insert LF
737 DeleteChars(pos + 1, 1); // Delete CR
738 }
739 }
740 } else if (cb.CharAt(pos) == '\n') {
741 // LF
742 if (eolModeSet == SC_EOL_CRLF) {
743 InsertString(pos, "\r", 1); // Insert CR
744 pos++;
745 } else if (eolModeSet == SC_EOL_CR) {
746 InsertString(pos, "\r", 1); // Insert CR
747 DeleteChars(pos + 1, 1); // Delete LF
748 }
749 }
750 }
751
752 EndUndoAction();
753 }
754
755 int Document::ParaDown(int pos) {
756 int line = LineFromPosition(pos);
757 while (line < LinesTotal() && LineStart(line) != LineEnd(line)) { // skip non-empty lines
758 line++;
759 }
760 while (line < LinesTotal() && LineStart(line) == LineEnd(line)) { // skip empty lines
761 line++;
762 }
763 if (line < LinesTotal())
764 return LineStart(line);
765 else // end of a document
766 return LineEnd(line-1);
767 }
768
769 int Document::ParaUp(int pos) {
770 int line = LineFromPosition(pos);
771 line--;
772 while (line >= 0 && LineStart(line) == LineEnd(line)) { // skip empty lines
773 line--;
774 }
775 while (line >= 0 && LineStart(line) != LineEnd(line)) { // skip non-empty lines
776 line--;
777 }
778 line++;
779 return LineStart(line);
780 }
781
782 Document::charClassification Document::WordCharClass(unsigned char ch) {
783 if ((SC_CP_UTF8 == dbcsCodePage) && (ch >= 0x80))
784 return ccWord;
785 return charClass[ch];
786 }
787
788 /**
789 * Used by commmands that want to select whole words.
790 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
791 */
792 int Document::ExtendWordSelect(int pos, int delta, bool onlyWordCharacters) {
793 charClassification ccStart = ccWord;
794 if (delta < 0) {
795 if (!onlyWordCharacters)
796 ccStart = WordCharClass(cb.CharAt(pos-1));
797 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart))
798 pos--;
799 } else {
800 if (!onlyWordCharacters)
801 ccStart = WordCharClass(cb.CharAt(pos));
802 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
803 pos++;
804 }
805 return MovePositionOutsideChar(pos, delta);
806 }
807
808 /**
809 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
810 * (delta < 0).
811 * This is looking for a transition between character classes although there is also some
812 * additional movement to transit white space.
813 * Used by cursor movement by word commands.
814 */
815 int Document::NextWordStart(int pos, int delta) {
816 if (delta < 0) {
817 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccSpace))
818 pos--;
819 if (pos > 0) {
820 charClassification ccStart = WordCharClass(cb.CharAt(pos-1));
821 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) {
822 pos--;
823 }
824 }
825 } else {
826 charClassification ccStart = WordCharClass(cb.CharAt(pos));
827 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
828 pos++;
829 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccSpace))
830 pos++;
831 }
832 return pos;
833 }
834
835 /**
836 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
837 * (delta < 0).
838 * This is looking for a transition between character classes although there is also some
839 * additional movement to transit white space.
840 * Used by cursor movement by word commands.
841 */
842 int Document::NextWordEnd(int pos, int delta) {
843 if (delta < 0) {
844 if (pos > 0) {
845 charClassification ccStart = WordCharClass(cb.CharAt(pos-1));
846 if (ccStart != ccSpace) {
847 while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) {
848 pos--;
849 }
850 }
851 while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccSpace) {
852 pos--;
853 }
854 }
855 } else {
856 while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccSpace) {
857 pos++;
858 }
859 if (pos < Length()) {
860 charClassification ccStart = WordCharClass(cb.CharAt(pos));
861 while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) {
862 pos++;
863 }
864 }
865 }
866 return pos;
867 }
868
869 /**
870 * Check that the character at the given position is a word or punctuation character and that
871 * the previous character is of a different character class.
872 */
873 bool Document::IsWordStartAt(int pos) {
874 if (pos > 0) {
875 charClassification ccPos = WordCharClass(CharAt(pos));
876 return (ccPos == ccWord || ccPos == ccPunctuation) &&
877 (ccPos != WordCharClass(CharAt(pos - 1)));
878 }
879 return true;
880 }
881
882 /**
883 * Check that the character at the given position is a word or punctuation character and that
884 * the next character is of a different character class.
885 */
886 bool Document::IsWordEndAt(int pos) {
887 if (pos < Length()) {
888 charClassification ccPrev = WordCharClass(CharAt(pos-1));
889 return (ccPrev == ccWord || ccPrev == ccPunctuation) &&
890 (ccPrev != WordCharClass(CharAt(pos)));
891 }
892 return true;
893 }
894
895 /**
896 * Check that the given range is has transitions between character classes at both
897 * ends and where the characters on the inside are word or punctuation characters.
898 */
899 bool Document::IsWordAt(int start, int end) {
900 return IsWordStartAt(start) && IsWordEndAt(end);
901 }
902
903 // The comparison and case changing functions here assume ASCII
904 // or extended ASCII such as the normal Windows code page.
905
906 static inline char MakeUpperCase(char ch) {
907 if (ch < 'a' || ch > 'z')
908 return ch;
909 else
910 return static_cast<char>(ch - 'a' + 'A');
911 }
912
913 static inline char MakeLowerCase(char ch) {
914 if (ch < 'A' || ch > 'Z')
915 return ch;
916 else
917 return static_cast<char>(ch - 'A' + 'a');
918 }
919
920 // Define a way for the Regular Expression code to access the document
921 class DocumentIndexer : public CharacterIndexer {
922 Document *pdoc;
923 int end;
924 public:
925 DocumentIndexer(Document *pdoc_, int end_) :
926 pdoc(pdoc_), end(end_) {
927 }
928
929 virtual char CharAt(int index) {
930 if (index < 0 || index >= end)
931 return 0;
932 else
933 return pdoc->CharAt(index);
934 }
935 };
936
937 /**
938 * Find text in document, supporting both forward and backward
939 * searches (just pass minPos > maxPos to do a backward search)
940 * Has not been tested with backwards DBCS searches yet.
941 */
942 long Document::FindText(int minPos, int maxPos, const char *s,
943 bool caseSensitive, bool word, bool wordStart, bool regExp, bool posix,
944 int *length) {
945 if (regExp) {
946 if (!pre)
947 pre = new RESearch();
948 if (!pre)
949 return -1;
950
951 int increment = (minPos <= maxPos) ? 1 : -1;
952
953 int startPos = minPos;
954 int endPos = maxPos;
955
956 // Range endpoints should not be inside DBCS characters, but just in case, move them.
957 startPos = MovePositionOutsideChar(startPos, 1, false);
958 endPos = MovePositionOutsideChar(endPos, 1, false);
959
960 const char *errmsg = pre->Compile(s, *length, caseSensitive, posix);
961 if (errmsg) {
962 return -1;
963 }
964 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
965 // Replace first '.' with '-' in each property file variable reference:
966 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
967 // Replace: $(\1-\2)
968 int lineRangeStart = LineFromPosition(startPos);
969 int lineRangeEnd = LineFromPosition(endPos);
970 if ((increment == 1) &&
971 (startPos >= LineEnd(lineRangeStart)) &&
972 (lineRangeStart < lineRangeEnd)) {
973 // the start position is at end of line or between line end characters.
974 lineRangeStart++;
975 startPos = LineStart(lineRangeStart);
976 }
977 int pos = -1;
978 int lenRet = 0;
979 char searchEnd = s[*length - 1];
980 int lineRangeBreak = lineRangeEnd + increment;
981 for (int line = lineRangeStart; line != lineRangeBreak; line += increment) {
982 int startOfLine = LineStart(line);
983 int endOfLine = LineEnd(line);
984 if (increment == 1) {
985 if (line == lineRangeStart) {
986 if ((startPos != startOfLine) && (s[0] == '^'))
987 continue; // Can't match start of line if start position after start of line
988 startOfLine = startPos;
989 }
990 if (line == lineRangeEnd) {
991 if ((endPos != endOfLine) && (searchEnd == '$'))
992 continue; // Can't match end of line if end position before end of line
993 endOfLine = endPos;
994 }
995 } else {
996 if (line == lineRangeEnd) {
997 if ((endPos != startOfLine) && (s[0] == '^'))
998 continue; // Can't match start of line if end position after start of line
999 startOfLine = endPos;
1000 }
1001 if (line == lineRangeStart) {
1002 if ((startPos != endOfLine) && (searchEnd == '$'))
1003 continue; // Can't match end of line if start position before end of line
1004 endOfLine = startPos+1;
1005 }
1006 }
1007
1008 DocumentIndexer di(this, endOfLine);
1009 int success = pre->Execute(di, startOfLine, endOfLine);
1010 if (success) {
1011 pos = pre->bopat[0];
1012 lenRet = pre->eopat[0] - pre->bopat[0];
1013 if (increment == -1) {
1014 // Check for the last match on this line.
1015 int repetitions = 1000; // Break out of infinite loop
1016 while (success && (pre->eopat[0] <= (endOfLine+1)) && (repetitions--)) {
1017 success = pre->Execute(di, pos+1, endOfLine+1);
1018 if (success) {
1019 if (pre->eopat[0] <= (minPos+1)) {
1020 pos = pre->bopat[0];
1021 lenRet = pre->eopat[0] - pre->bopat[0];
1022 } else {
1023 success = 0;
1024 }
1025 }
1026 }
1027 }
1028 break;
1029 }
1030 }
1031 *length = lenRet;
1032 return pos;
1033
1034 } else {
1035
1036 bool forward = minPos <= maxPos;
1037 int increment = forward ? 1 : -1;
1038
1039 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1040 int startPos = MovePositionOutsideChar(minPos, increment, false);
1041 int endPos = MovePositionOutsideChar(maxPos, increment, false);
1042
1043 // Compute actual search ranges needed
1044 int lengthFind = *length;
1045 if (lengthFind == -1)
1046 lengthFind = static_cast<int>(strlen(s));
1047 int endSearch = endPos;
1048 if (startPos <= endPos) {
1049 endSearch = endPos - lengthFind + 1;
1050 }
1051 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
1052 char firstChar = s[0];
1053 if (!caseSensitive)
1054 firstChar = static_cast<char>(MakeUpperCase(firstChar));
1055 int pos = startPos;
1056 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
1057 char ch = CharAt(pos);
1058 if (caseSensitive) {
1059 if (ch == firstChar) {
1060 bool found = true;
1061 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
1062 ch = CharAt(pos + posMatch);
1063 if (ch != s[posMatch])
1064 found = false;
1065 }
1066 if (found) {
1067 if ((!word && !wordStart) ||
1068 word && IsWordAt(pos, pos + lengthFind) ||
1069 wordStart && IsWordStartAt(pos))
1070 return pos;
1071 }
1072 }
1073 } else {
1074 if (MakeUpperCase(ch) == firstChar) {
1075 bool found = true;
1076 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
1077 ch = CharAt(pos + posMatch);
1078 if (MakeUpperCase(ch) != MakeUpperCase(s[posMatch]))
1079 found = false;
1080 }
1081 if (found) {
1082 if ((!word && !wordStart) ||
1083 word && IsWordAt(pos, pos + lengthFind) ||
1084 wordStart && IsWordStartAt(pos))
1085 return pos;
1086 }
1087 }
1088 }
1089 pos += increment;
1090 if (dbcsCodePage && (pos >= 0)) {
1091 // Ensure trying to match from start of character
1092 pos = MovePositionOutsideChar(pos, increment, false);
1093 }
1094 }
1095 }
1096 //Platform::DebugPrintf("Not found\n");
1097 return -1;
1098 }
1099
1100 const char *Document::SubstituteByPosition(const char *text, int *length) {
1101 if (!pre)
1102 return 0;
1103 delete []substituted;
1104 substituted = 0;
1105 DocumentIndexer di(this, Length());
1106 if (!pre->GrabMatches(di))
1107 return 0;
1108 unsigned int lenResult = 0;
1109 for (int i = 0; i < *length; i++) {
1110 if (text[i] == '\\') {
1111 if (text[i + 1] >= '1' && text[i + 1] <= '9') {
1112 unsigned int patNum = text[i + 1] - '0';
1113 lenResult += pre->eopat[patNum] - pre->bopat[patNum];
1114 i++;
1115 } else {
1116 switch (text[i + 1]) {
1117 case 'a':
1118 case 'b':
1119 case 'f':
1120 case 'n':
1121 case 'r':
1122 case 't':
1123 case 'v':
1124 i++;
1125 }
1126 lenResult++;
1127 }
1128 } else {
1129 lenResult++;
1130 }
1131 }
1132 substituted = new char[lenResult + 1];
1133 if (!substituted)
1134 return 0;
1135 char *o = substituted;
1136 for (int j = 0; j < *length; j++) {
1137 if (text[j] == '\\') {
1138 if (text[j + 1] >= '1' && text[j + 1] <= '9') {
1139 unsigned int patNum = text[j + 1] - '0';
1140 unsigned int len = pre->eopat[patNum] - pre->bopat[patNum];
1141 if (pre->pat[patNum]) // Will be null if try for a match that did not occur
1142 memcpy(o, pre->pat[patNum], len);
1143 o += len;
1144 j++;
1145 } else {
1146 j++;
1147 switch (text[j]) {
1148 case 'a':
1149 *o++ = '\a';
1150 break;
1151 case 'b':
1152 *o++ = '\b';
1153 break;
1154 case 'f':
1155 *o++ = '\f';
1156 break;
1157 case 'n':
1158 *o++ = '\n';
1159 break;
1160 case 'r':
1161 *o++ = '\r';
1162 break;
1163 case 't':
1164 *o++ = '\t';
1165 break;
1166 case 'v':
1167 *o++ = '\v';
1168 break;
1169 default:
1170 *o++ = '\\';
1171 j--;
1172 }
1173 }
1174 } else {
1175 *o++ = text[j];
1176 }
1177 }
1178 *o = '\0';
1179 *length = lenResult;
1180 return substituted;
1181 }
1182
1183 int Document::LinesTotal() {
1184 return cb.Lines();
1185 }
1186
1187 void Document::ChangeCase(Range r, bool makeUpperCase) {
1188 for (int pos = r.start; pos < r.end; pos++) {
1189 int len = LenChar(pos);
1190 if (dbcsCodePage && (len > 1)) {
1191 pos += len;
1192 } else {
1193 char ch = CharAt(pos);
1194 if (makeUpperCase) {
1195 if (IsLowerCase(ch)) {
1196 ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
1197 }
1198 } else {
1199 if (IsUpperCase(ch)) {
1200 ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
1201 }
1202 }
1203 }
1204 }
1205 }
1206
1207 void Document::SetDefaultCharClasses(bool includeWordClass) {
1208 // Initialize all char classes to default values
1209 for (int ch = 0; ch < 256; ch++) {
1210 if (ch == '\r' || ch == '\n')
1211 charClass[ch] = ccNewLine;
1212 else if (ch < 0x20 || ch == ' ')
1213 charClass[ch] = ccSpace;
1214 else if (includeWordClass && (ch >= 0x80 || isalnum(ch) || ch == '_'))
1215 charClass[ch] = ccWord;
1216 else
1217 charClass[ch] = ccPunctuation;
1218 }
1219 }
1220
1221 void Document::SetCharClasses(const unsigned char *chars, charClassification newCharClass) {
1222 // Apply the newCharClass to the specifed chars
1223 if (chars) {
1224 while (*chars) {
1225 charClass[*chars] = newCharClass;
1226 chars++;
1227 }
1228 }
1229 }
1230
1231 void Document::SetStylingBits(int bits) {
1232 stylingBits = bits;
1233 stylingBitsMask = 0;
1234 for (int bit = 0; bit < stylingBits; bit++) {
1235 stylingBitsMask <<= 1;
1236 stylingBitsMask |= 1;
1237 }
1238 }
1239
1240 void Document::StartStyling(int position, char mask) {
1241 stylingMask = mask;
1242 endStyled = position;
1243 }
1244
1245 bool Document::SetStyleFor(int length, char style) {
1246 if (enteredCount != 0) {
1247 return false;
1248 } else {
1249 enteredCount++;
1250 style &= stylingMask;
1251 int prevEndStyled = endStyled;
1252 if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {
1253 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1254 prevEndStyled, length);
1255 NotifyModified(mh);
1256 }
1257 endStyled += length;
1258 enteredCount--;
1259 return true;
1260 }
1261 }
1262
1263 bool Document::SetStyles(int length, char *styles) {
1264 if (enteredCount != 0) {
1265 return false;
1266 } else {
1267 enteredCount++;
1268 int prevEndStyled = endStyled;
1269 bool didChange = false;
1270 int lastChange = 0;
1271 for (int iPos = 0; iPos < length; iPos++, endStyled++) {
1272 PLATFORM_ASSERT(endStyled < Length());
1273 if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
1274 didChange = true;
1275 lastChange = iPos;
1276 }
1277 }
1278 if (didChange) {
1279 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1280 prevEndStyled, lastChange);
1281 NotifyModified(mh);
1282 }
1283 enteredCount--;
1284 return true;
1285 }
1286 }
1287
1288 bool Document::EnsureStyledTo(int pos) {
1289 if (pos > GetEndStyled()) {
1290 IncrementStyleClock();
1291 // Ask the watchers to style, and stop as soon as one responds.
1292 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++) {
1293 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
1294 }
1295 }
1296 return pos <= GetEndStyled();
1297 }
1298
1299 void Document::IncrementStyleClock() {
1300 styleClock++;
1301 if (styleClock > 0x100000) {
1302 styleClock = 0;
1303 }
1304 }
1305
1306 bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
1307 for (int i = 0; i < lenWatchers; i++) {
1308 if ((watchers[i].watcher == watcher) &&
1309 (watchers[i].userData == userData))
1310 return false;
1311 }
1312 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
1313 if (!pwNew)
1314 return false;
1315 for (int j = 0; j < lenWatchers; j++)
1316 pwNew[j] = watchers[j];
1317 pwNew[lenWatchers].watcher = watcher;
1318 pwNew[lenWatchers].userData = userData;
1319 delete []watchers;
1320 watchers = pwNew;
1321 lenWatchers++;
1322 return true;
1323 }
1324
1325 bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
1326 for (int i = 0; i < lenWatchers; i++) {
1327 if ((watchers[i].watcher == watcher) &&
1328 (watchers[i].userData == userData)) {
1329 if (lenWatchers == 1) {
1330 delete []watchers;
1331 watchers = 0;
1332 lenWatchers = 0;
1333 } else {
1334 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
1335 if (!pwNew)
1336 return false;
1337 for (int j = 0; j < lenWatchers - 1; j++) {
1338 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
1339 }
1340 delete []watchers;
1341 watchers = pwNew;
1342 lenWatchers--;
1343 }
1344 return true;
1345 }
1346 }
1347 return false;
1348 }
1349
1350 void Document::NotifyModifyAttempt() {
1351 for (int i = 0; i < lenWatchers; i++) {
1352 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
1353 }
1354 }
1355
1356 void Document::NotifySavePoint(bool atSavePoint) {
1357 for (int i = 0; i < lenWatchers; i++) {
1358 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
1359 }
1360 }
1361
1362 void Document::NotifyModified(DocModification mh) {
1363 for (int i = 0; i < lenWatchers; i++) {
1364 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
1365 }
1366 }
1367
1368 bool Document::IsWordPartSeparator(char ch) {
1369 return (WordCharClass(ch) == ccWord) && IsPunctuation(ch);
1370 }
1371
1372 int Document::WordPartLeft(int pos) {
1373 if (pos > 0) {
1374 --pos;
1375 char startChar = cb.CharAt(pos);
1376 if (IsWordPartSeparator(startChar)) {
1377 while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {
1378 --pos;
1379 }
1380 }
1381 if (pos > 0) {
1382 startChar = cb.CharAt(pos);
1383 --pos;
1384 if (IsLowerCase(startChar)) {
1385 while (pos > 0 && IsLowerCase(cb.CharAt(pos)))
1386 --pos;
1387 if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos)))
1388 ++pos;
1389 } else if (IsUpperCase(startChar)) {
1390 while (pos > 0 && IsUpperCase(cb.CharAt(pos)))
1391 --pos;
1392 if (!IsUpperCase(cb.CharAt(pos)))
1393 ++pos;
1394 } else if (IsADigit(startChar)) {
1395 while (pos > 0 && IsADigit(cb.CharAt(pos)))
1396 --pos;
1397 if (!IsADigit(cb.CharAt(pos)))
1398 ++pos;
1399 } else if (IsPunctuation(startChar)) {
1400 while (pos > 0 && IsPunctuation(cb.CharAt(pos)))
1401 --pos;
1402 if (!IsPunctuation(cb.CharAt(pos)))
1403 ++pos;
1404 } else if (isspacechar(startChar)) {
1405 while (pos > 0 && isspacechar(cb.CharAt(pos)))
1406 --pos;
1407 if (!isspacechar(cb.CharAt(pos)))
1408 ++pos;
1409 } else if (!isascii(startChar)) {
1410 while (pos > 0 && !isascii(cb.CharAt(pos)))
1411 --pos;
1412 if (isascii(cb.CharAt(pos)))
1413 ++pos;
1414 } else {
1415 ++pos;
1416 }
1417 }
1418 }
1419 return pos;
1420 }
1421
1422 int Document::WordPartRight(int pos) {
1423 char startChar = cb.CharAt(pos);
1424 int length = Length();
1425 if (IsWordPartSeparator(startChar)) {
1426 while (pos < length && IsWordPartSeparator(cb.CharAt(pos)))
1427 ++pos;
1428 startChar = cb.CharAt(pos);
1429 }
1430 if (!isascii(startChar)) {
1431 while (pos < length && !isascii(cb.CharAt(pos)))
1432 ++pos;
1433 } else if (IsLowerCase(startChar)) {
1434 while (pos < length && IsLowerCase(cb.CharAt(pos)))
1435 ++pos;
1436 } else if (IsUpperCase(startChar)) {
1437 if (IsLowerCase(cb.CharAt(pos + 1))) {
1438 ++pos;
1439 while (pos < length && IsLowerCase(cb.CharAt(pos)))
1440 ++pos;
1441 } else {
1442 while (pos < length && IsUpperCase(cb.CharAt(pos)))
1443 ++pos;
1444 }
1445 if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1)))
1446 --pos;
1447 } else if (IsADigit(startChar)) {
1448 while (pos < length && IsADigit(cb.CharAt(pos)))
1449 ++pos;
1450 } else if (IsPunctuation(startChar)) {
1451 while (pos < length && IsPunctuation(cb.CharAt(pos)))
1452 ++pos;
1453 } else if (isspacechar(startChar)) {
1454 while (pos < length && isspacechar(cb.CharAt(pos)))
1455 ++pos;
1456 } else {
1457 ++pos;
1458 }
1459 return pos;
1460 }
1461
1462 bool IsLineEndChar(char c) {
1463 return (c == '\n' || c == '\r');
1464 }
1465
1466 int Document::ExtendStyleRange(int pos, int delta, bool singleLine) {
1467 int sStart = cb.StyleAt(pos);
1468 if (delta < 0) {
1469 while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )
1470 pos--;
1471 pos++;
1472 } else {
1473 while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )
1474 pos++;
1475 }
1476 return pos;
1477 }