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