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