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