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