]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
This form of the event cloning patch survived my
[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-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 (len == 0)
361 return;
362 if ((pos + len) > Length())
363 return;
364 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
365 enteredReadOnlyCount++;
366 NotifyModifyAttempt();
367 enteredReadOnlyCount--;
368 }
369 if (enteredCount == 0) {
370 enteredCount++;
371 if (!cb.IsReadOnly()) {
372 NotifyModified(
373 DocModification(
374 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
375 pos, len,
376 0, 0));
377 int prevLinesTotal = LinesTotal();
378 bool startSavePoint = cb.IsSavePoint();
379 const char *text = cb.DeleteChars(pos * 2, len * 2);
380 if (startSavePoint && cb.IsCollectingUndo())
381 NotifySavePoint(!startSavePoint);
382 if ((pos < Length()) || (pos == 0))
383 ModifiedAt(pos);
384 else
385 ModifiedAt(pos-1);
386 NotifyModified(
387 DocModification(
388 SC_MOD_DELETETEXT | SC_PERFORMED_USER,
389 pos, len,
390 LinesTotal() - prevLinesTotal, text));
391 }
392 enteredCount--;
393 }
394 }
395
396 void Document::InsertStyledString(int position, char *s, int insertLength) {
397 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
398 enteredReadOnlyCount++;
399 NotifyModifyAttempt();
400 enteredReadOnlyCount--;
401 }
402 if (enteredCount == 0) {
403 enteredCount++;
404 if (!cb.IsReadOnly()) {
405 NotifyModified(
406 DocModification(
407 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
408 position / 2, insertLength / 2,
409 0, 0));
410 int prevLinesTotal = LinesTotal();
411 bool startSavePoint = cb.IsSavePoint();
412 const char *text = cb.InsertString(position, s, insertLength);
413 if (startSavePoint && cb.IsCollectingUndo())
414 NotifySavePoint(!startSavePoint);
415 ModifiedAt(position / 2);
416 NotifyModified(
417 DocModification(
418 SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
419 position / 2, insertLength / 2,
420 LinesTotal() - prevLinesTotal, text));
421 }
422 enteredCount--;
423 }
424 }
425
426 int Document::Undo() {
427 int newPos = 0;
428 if (enteredCount == 0) {
429 enteredCount++;
430 bool startSavePoint = cb.IsSavePoint();
431 int steps = cb.StartUndo();
432 //Platform::DebugPrintf("Steps=%d\n", steps);
433 for (int step = 0; step < steps; step++) {
434 int prevLinesTotal = LinesTotal();
435 const Action &action = cb.GetUndoStep();
436 if (action.at == removeAction) {
437 NotifyModified(DocModification(
438 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
439 } else {
440 NotifyModified(DocModification(
441 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
442 }
443 cb.PerformUndoStep();
444 int cellPosition = action.position / 2;
445 ModifiedAt(cellPosition);
446 newPos = cellPosition;
447
448 int modFlags = SC_PERFORMED_UNDO;
449 // With undo, an insertion action becomes a deletion notification
450 if (action.at == removeAction) {
451 newPos += action.lenData;
452 modFlags |= SC_MOD_INSERTTEXT;
453 } else {
454 modFlags |= SC_MOD_DELETETEXT;
455 }
456 if (step == steps - 1)
457 modFlags |= SC_LASTSTEPINUNDOREDO;
458 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
459 LinesTotal() - prevLinesTotal, action.data));
460 }
461
462 bool endSavePoint = cb.IsSavePoint();
463 if (startSavePoint != endSavePoint)
464 NotifySavePoint(endSavePoint);
465 enteredCount--;
466 }
467 return newPos;
468 }
469
470 int Document::Redo() {
471 int newPos = 0;
472 if (enteredCount == 0) {
473 enteredCount++;
474 bool startSavePoint = cb.IsSavePoint();
475 int steps = cb.StartRedo();
476 for (int step = 0; step < steps; step++) {
477 int prevLinesTotal = LinesTotal();
478 const Action &action = cb.GetRedoStep();
479 if (action.at == insertAction) {
480 NotifyModified(DocModification(
481 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
482 } else {
483 NotifyModified(DocModification(
484 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
485 }
486 cb.PerformRedoStep();
487 ModifiedAt(action.position / 2);
488 newPos = action.position / 2;
489
490 int modFlags = SC_PERFORMED_REDO;
491 if (action.at == insertAction) {
492 newPos += action.lenData;
493 modFlags |= SC_MOD_INSERTTEXT;
494 } else {
495 modFlags |= SC_MOD_DELETETEXT;
496 }
497 if (step == steps - 1)
498 modFlags |= SC_LASTSTEPINUNDOREDO;
499 NotifyModified(
500 DocModification(modFlags, action.position / 2, action.lenData,
501 LinesTotal() - prevLinesTotal, action.data));
502 }
503
504 bool endSavePoint = cb.IsSavePoint();
505 if (startSavePoint != endSavePoint)
506 NotifySavePoint(endSavePoint);
507 enteredCount--;
508 }
509 return newPos;
510 }
511
512 void Document::InsertChar(int pos, char ch) {
513 char chs[2];
514 chs[0] = ch;
515 chs[1] = 0;
516 InsertStyledString(pos*2, chs, 2);
517 }
518
519 // Insert a null terminated string
520 void Document::InsertString(int position, const char *s) {
521 InsertString(position, s, strlen(s));
522 }
523
524 // Insert a string with a length
525 void Document::InsertString(int position, const char *s, int insertLength) {
526 char *sWithStyle = new char[insertLength * 2];
527 if (sWithStyle) {
528 for (int i = 0; i < insertLength; i++) {
529 sWithStyle[i*2] = s[i];
530 sWithStyle[i*2 + 1] = 0;
531 }
532 InsertStyledString(position*2, sWithStyle, insertLength*2);
533 delete []sWithStyle;
534 }
535 }
536
537 void Document::ChangeChar(int pos, char ch) {
538 DeleteChars(pos, 1);
539 InsertChar(pos, ch);
540 }
541
542 void Document::DelChar(int pos) {
543 DeleteChars(pos, LenChar(pos));
544 }
545
546 int Document::DelCharBack(int pos) {
547 if (pos <= 0) {
548 return pos;
549 } else if (IsCrLf(pos - 2)) {
550 DeleteChars(pos - 2, 2);
551 return pos - 2;
552 } else if (SC_CP_UTF8 == dbcsCodePage) {
553 int startChar = MovePositionOutsideChar(pos - 1, -1, false);
554 DeleteChars(startChar, pos - startChar);
555 return startChar;
556 } else if (IsDBCS(pos - 1)) {
557 DeleteChars(pos - 2, 2);
558 return pos - 2;
559 } else {
560 DeleteChars(pos - 1, 1);
561 return pos - 1;
562 }
563 }
564
565 static bool isindentchar(char ch) {
566 return (ch == ' ') || (ch == '\t');
567 }
568
569 static int NextTab(int pos, int tabSize) {
570 return ((pos / tabSize) + 1) * tabSize;
571 }
572
573 static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
574 length--; // ensure space for \0
575 if (!insertSpaces) {
576 while ((indent >= tabSize) && (length > 0)) {
577 *linebuf++ = '\t';
578 indent -= tabSize;
579 length--;
580 }
581 }
582 while ((indent > 0) && (length > 0)) {
583 *linebuf++ = ' ';
584 indent--;
585 length--;
586 }
587 *linebuf = '\0';
588 }
589
590 int Document::GetLineIndentation(int line) {
591 int indent = 0;
592 if ((line >= 0) && (line < LinesTotal())) {
593 int lineStart = LineStart(line);
594 int length = Length();
595 for (int i = lineStart;i < length;i++) {
596 char ch = cb.CharAt(i);
597 if (ch == ' ')
598 indent++;
599 else if (ch == '\t')
600 indent = NextTab(indent, tabInChars);
601 else
602 return indent;
603 }
604 }
605 return indent;
606 }
607
608 void Document::SetLineIndentation(int line, int indent) {
609 int indentOfLine = GetLineIndentation(line);
610 if (indent < 0)
611 indent = 0;
612 if (indent != indentOfLine) {
613 char linebuf[1000];
614 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
615 int thisLineStart = LineStart(line);
616 int indentPos = GetLineIndentPosition(line);
617 DeleteChars(thisLineStart, indentPos - thisLineStart);
618 InsertString(thisLineStart, linebuf);
619 }
620 }
621
622 int Document::GetLineIndentPosition(int line) {
623 if (line < 0)
624 return 0;
625 int pos = LineStart(line);
626 int length = Length();
627 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
628 pos++;
629 }
630 return pos;
631 }
632
633 int Document::GetColumn(int pos) {
634 int column = 0;
635 int line = LineFromPosition(pos);
636 if ((line >= 0) && (line < LinesTotal())) {
637 for (int i = LineStart(line);i < pos;i++) {
638 char ch = cb.CharAt(i);
639 if (ch == '\t')
640 column = NextTab(column, tabInChars);
641 else if (ch == '\r')
642 return column;
643 else if (ch == '\n')
644 return column;
645 else
646 column++;
647 }
648 }
649 return column;
650 }
651
652 void Document::Indent(bool forwards, int lineBottom, int lineTop) {
653 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
654 for (int line = lineBottom; line >= lineTop; line--) {
655 int indentOfLine = GetLineIndentation(line);
656 if (forwards)
657 SetLineIndentation(line, indentOfLine + IndentSize());
658 else
659 SetLineIndentation(line, indentOfLine - IndentSize());
660 }
661 }
662
663 void Document::ConvertLineEnds(int eolModeSet) {
664 BeginUndoAction();
665 for (int pos = 0; pos < Length(); pos++) {
666 if (cb.CharAt(pos) == '\r') {
667 if (cb.CharAt(pos + 1) == '\n') {
668 if (eolModeSet != SC_EOL_CRLF) {
669 DeleteChars(pos, 2);
670 if (eolModeSet == SC_EOL_CR)
671 InsertString(pos, "\r", 1);
672 else
673 InsertString(pos, "\n", 1);
674 } else {
675 pos++;
676 }
677 } else {
678 if (eolModeSet != SC_EOL_CR) {
679 DeleteChars(pos, 1);
680 if (eolModeSet == SC_EOL_CRLF) {
681 InsertString(pos, "\r\n", 2);
682 pos++;
683 } else {
684 InsertString(pos, "\n", 1);
685 }
686 }
687 }
688 } else if (cb.CharAt(pos) == '\n') {
689 if (eolModeSet != SC_EOL_LF) {
690 DeleteChars(pos, 1);
691 if (eolModeSet == SC_EOL_CRLF) {
692 InsertString(pos, "\r\n", 2);
693 pos++;
694 } else {
695 InsertString(pos, "\r", 1);
696 }
697 }
698 }
699 }
700 EndUndoAction();
701 }
702
703 bool Document::IsWordChar(unsigned char ch) {
704 if ((SC_CP_UTF8 == dbcsCodePage) && (ch > 0x80))
705 return true;
706 return wordchars[ch];
707 }
708
709 int Document::ExtendWordSelect(int pos, int delta) {
710 if (delta < 0) {
711 while (pos > 0 && IsWordChar(cb.CharAt(pos - 1)))
712 pos--;
713 } else {
714 while (pos < (Length()) && IsWordChar(cb.CharAt(pos)))
715 pos++;
716 }
717 return pos;
718 }
719
720 int Document::NextWordStart(int pos, int delta) {
721 if (delta < 0) {
722 while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t'))
723 pos--;
724 if (isspacechar(cb.CharAt(pos - 1))) { // Back up to previous line
725 while (pos > 0 && isspacechar(cb.CharAt(pos - 1)))
726 pos--;
727 } else {
728 bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1));
729 while (pos > 0 && !isspacechar(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1))))
730 pos--;
731 }
732 } else {
733 bool startAtWordChar = IsWordChar(cb.CharAt(pos));
734 while (pos < (Length()) && isspacechar(cb.CharAt(pos)))
735 pos++;
736 while (pos < (Length()) && !isspacechar(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos))))
737 pos++;
738 while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t'))
739 pos++;
740 }
741 return pos;
742 }
743
744 /**
745 * Check that the character before the given position
746 * is not a word character.
747 */
748 bool Document::IsWordStartAt(int pos) {
749 if (pos > 0) {
750 return !IsWordChar(CharAt(pos - 1));
751 }
752 return true;
753 }
754
755 /**
756 * Check that the character after the given position
757 * is not a word character.
758 */
759 bool Document::IsWordEndAt(int pos) {
760 if (pos < Length() - 1) {
761 return !IsWordChar(CharAt(pos));
762 }
763 return true;
764 }
765
766 /**
767 * Check that the given range is delimited by
768 * non word characters.
769 */
770 bool Document::IsWordAt(int start, int end) {
771 return IsWordStartAt(start) && IsWordEndAt(end);
772 }
773
774 // The comparison and case changing functions here assume ASCII
775 // or extended ASCII such as the normal Windows code page.
776
777 static inline char MakeUpperCase(char ch) {
778 if (ch < 'a' || ch > 'z')
779 return ch;
780 else
781 return static_cast<char>(ch - 'a' + 'A');
782 }
783
784 static inline char MakeLowerCase(char ch) {
785 if (ch < 'A' || ch > 'Z')
786 return ch;
787 else
788 return static_cast<char>(ch - 'A' + 'a');
789 }
790
791 // Define a way for the Regular Expression code to access the document
792 class DocumentIndexer : public CharacterIndexer {
793 Document *pdoc;
794 int end;
795 public:
796 DocumentIndexer(Document *pdoc_, int end_) :
797 pdoc(pdoc_), end(end_) {}
798
799 virtual char CharAt(int index) {
800 if (index < 0 || index >= end)
801 return 0;
802 else
803 return pdoc->CharAt(index);
804 }
805 };
806
807 /**
808 * Find text in document, supporting both forward and backward
809 * searches (just pass minPos > maxPos to do a backward search)
810 * Has not been tested with backwards DBCS searches yet.
811 */
812 long Document::FindText(int minPos, int maxPos, const char *s,
813 bool caseSensitive, bool word, bool wordStart, bool regExp,
814 int *length) {
815 if (regExp) {
816 if (!pre)
817 pre = new RESearch();
818 if (!pre)
819 return -1;
820
821 int startPos;
822 int endPos;
823
824 if (minPos <= maxPos) {
825 startPos = minPos;
826 endPos = maxPos;
827 } else {
828 startPos = maxPos;
829 endPos = minPos;
830 }
831
832 // Range endpoints should not be inside DBCS characters, but just in case, move them.
833 startPos = MovePositionOutsideChar(startPos, 1, false);
834 endPos = MovePositionOutsideChar(endPos, 1, false);
835
836 const char *errmsg = pre->Compile(s, *length, caseSensitive);
837 if (errmsg) {
838 return -1;
839 }
840 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
841 // Replace first '.' with '-' in each property file variable reference:
842 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
843 // Replace: $(\1-\2)
844 int lineRangeStart = LineFromPosition(startPos);
845 int lineRangeEnd = LineFromPosition(endPos);
846 if ((startPos >= LineEnd(lineRangeStart)) && (lineRangeStart < lineRangeEnd)) {
847 // the start position is at end of line or between line end characters.
848 lineRangeStart++;
849 startPos = LineStart(lineRangeStart);
850 }
851 int pos = -1;
852 int lenRet = 0;
853 char searchEnd = s[*length - 1];
854 if (*length == 1) {
855 // These produce empty selections so nudge them on if needed
856 if (s[0] == '^') {
857 if (startPos == LineStart(lineRangeStart))
858 startPos++;
859 } else if (s[0] == '$') {
860 if ((startPos == LineEnd(lineRangeStart)) && (lineRangeStart < lineRangeEnd))
861 startPos = LineStart(lineRangeStart + 1);
862 }
863 lineRangeStart = LineFromPosition(startPos);
864 lineRangeEnd = LineFromPosition(endPos);
865 }
866 for (int line = lineRangeStart; line <= lineRangeEnd; line++) {
867 int startOfLine = LineStart(line);
868 int endOfLine = LineEnd(line);
869 if (line == lineRangeStart) {
870 if ((startPos != startOfLine) && (s[0] == '^'))
871 continue; // Can't match start of line if start position after start of line
872 startOfLine = startPos;
873 }
874 if (line == lineRangeEnd) {
875 if ((endPos != endOfLine) && (searchEnd == '$'))
876 continue; // Can't match end of line if end position before end of line
877 endOfLine = endPos;
878 }
879 DocumentIndexer di(this, endOfLine);
880 int success = pre->Execute(di, startOfLine, endOfLine);
881 if (success) {
882 pos = pre->bopat[0];
883 lenRet = pre->eopat[0] - pre->bopat[0];
884 break;
885 }
886 }
887 *length = lenRet;
888 return pos;
889
890 } else {
891
892 bool forward = minPos <= maxPos;
893 int increment = forward ? 1 : -1;
894
895 // Range endpoints should not be inside DBCS characters, but just in case, move them.
896 int startPos = MovePositionOutsideChar(minPos, increment, false);
897 int endPos = MovePositionOutsideChar(maxPos, increment, false);
898
899 // Compute actual search ranges needed
900 int lengthFind = *length;
901 if (lengthFind == -1)
902 lengthFind = strlen(s);
903 int endSearch = endPos;
904 if (startPos <= endPos) {
905 endSearch = endPos - lengthFind + 1;
906 }
907 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
908 char firstChar = s[0];
909 if (!caseSensitive)
910 firstChar = static_cast<char>(MakeUpperCase(firstChar));
911 int pos = startPos;
912 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
913 char ch = CharAt(pos);
914 if (caseSensitive) {
915 if (ch == firstChar) {
916 bool found = true;
917 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
918 ch = CharAt(pos + posMatch);
919 if (ch != s[posMatch])
920 found = false;
921 }
922 if (found) {
923 if ((!word && !wordStart) ||
924 word && IsWordAt(pos, pos + lengthFind) ||
925 wordStart && IsWordStartAt(pos))
926 return pos;
927 }
928 }
929 } else {
930 if (MakeUpperCase(ch) == firstChar) {
931 bool found = true;
932 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
933 ch = CharAt(pos + posMatch);
934 if (MakeUpperCase(ch) != MakeUpperCase(s[posMatch]))
935 found = false;
936 }
937 if (found) {
938 if ((!word && !wordStart) ||
939 word && IsWordAt(pos, pos + lengthFind) ||
940 wordStart && IsWordStartAt(pos))
941 return pos;
942 }
943 }
944 }
945 pos += increment;
946 if (dbcsCodePage) {
947 // Ensure trying to match from start of character
948 pos = MovePositionOutsideChar(pos, increment, false);
949 }
950 }
951 }
952 //Platform::DebugPrintf("Not found\n");
953 return -1;
954 }
955
956 const char *Document::SubstituteByPosition(const char *text, int *length) {
957 if (!pre)
958 return 0;
959 delete []substituted;
960 substituted = 0;
961 DocumentIndexer di(this, Length());
962 if (!pre->GrabMatches(di))
963 return 0;
964 unsigned int lenResult = 0;
965 for (int i = 0; i < *length; i++) {
966 if ((text[i] == '\\') && (text[i + 1] >= '1' && text[i + 1] <= '9')) {
967 unsigned int patNum = text[i + 1] - '0';
968 lenResult += pre->eopat[patNum] - pre->bopat[patNum];
969 i++;
970 } else {
971 lenResult++;
972 }
973 }
974 substituted = new char[lenResult + 1];
975 if (!substituted)
976 return 0;
977 char *o = substituted;
978 for (int j = 0; j < *length; j++) {
979 if ((text[j] == '\\') && (text[j + 1] >= '1' && text[j + 1] <= '9')) {
980 unsigned int patNum = text[j + 1] - '0';
981 unsigned int len = pre->eopat[patNum] - pre->bopat[patNum];
982 if (pre->pat[patNum]) // Will be null if try for a match that did not occur
983 memcpy(o, pre->pat[patNum], len);
984 o += len;
985 j++;
986 } else {
987 *o++ = text[j];
988 }
989 }
990 *o = '\0';
991 *length = lenResult;
992 return substituted;
993 }
994
995 int Document::LinesTotal() {
996 return cb.Lines();
997 }
998
999 void Document::ChangeCase(Range r, bool makeUpperCase) {
1000 for (int pos = r.start; pos < r.end; pos++) {
1001 char ch = CharAt(pos);
1002 if (dbcsCodePage && IsDBCS(pos)) {
1003 pos += LenChar(pos);
1004 } else {
1005 if (makeUpperCase) {
1006 if (islower(ch)) {
1007 ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
1008 }
1009 } else {
1010 if (isupper(ch)) {
1011 ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
1012 }
1013 }
1014 }
1015 }
1016 }
1017
1018 void Document::SetWordChars(unsigned char *chars) {
1019 int ch;
1020 for (ch = 0; ch < 256; ch++) {
1021 wordchars[ch] = false;
1022 }
1023 if (chars) {
1024 while (*chars) {
1025 wordchars[*chars] = true;
1026 chars++;
1027 }
1028 } else {
1029 for (ch = 0; ch < 256; ch++) {
1030 wordchars[ch] = isalnum(ch) || ch == '_';
1031 }
1032 }
1033 }
1034
1035 void Document::SetStylingBits(int bits) {
1036 stylingBits = bits;
1037 stylingBitsMask = 0;
1038 for (int bit = 0; bit < stylingBits; bit++) {
1039 stylingBitsMask <<= 1;
1040 stylingBitsMask |= 1;
1041 }
1042 }
1043
1044 void Document::StartStyling(int position, char mask) {
1045 stylingMask = mask;
1046 endStyled = position;
1047 }
1048
1049 void Document::SetStyleFor(int length, char style) {
1050 if (enteredCount == 0) {
1051 enteredCount++;
1052 int prevEndStyled = endStyled;
1053 if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {
1054 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1055 prevEndStyled, length);
1056 NotifyModified(mh);
1057 }
1058 endStyled += length;
1059 enteredCount--;
1060 }
1061 }
1062
1063 void Document::SetStyles(int length, char *styles) {
1064 if (enteredCount == 0) {
1065 enteredCount++;
1066 int prevEndStyled = endStyled;
1067 bool didChange = false;
1068 for (int iPos = 0; iPos < length; iPos++, endStyled++) {
1069 if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
1070 didChange = true;
1071 }
1072 }
1073 if (didChange) {
1074 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1075 prevEndStyled, endStyled - prevEndStyled);
1076 NotifyModified(mh);
1077 }
1078 enteredCount--;
1079 }
1080 }
1081
1082 bool Document::EnsureStyledTo(int pos) {
1083 // Ask the watchers to style, and stop as soon as one responds.
1084 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++)
1085 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
1086 return pos <= GetEndStyled();
1087 }
1088
1089 bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
1090 for (int i = 0; i < lenWatchers; i++) {
1091 if ((watchers[i].watcher == watcher) &&
1092 (watchers[i].userData == userData))
1093 return false;
1094 }
1095 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
1096 if (!pwNew)
1097 return false;
1098 for (int j = 0; j < lenWatchers; j++)
1099 pwNew[j] = watchers[j];
1100 pwNew[lenWatchers].watcher = watcher;
1101 pwNew[lenWatchers].userData = userData;
1102 delete []watchers;
1103 watchers = pwNew;
1104 lenWatchers++;
1105 return true;
1106 }
1107
1108 bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
1109 for (int i = 0; i < lenWatchers; i++) {
1110 if ((watchers[i].watcher == watcher) &&
1111 (watchers[i].userData == userData)) {
1112 if (lenWatchers == 1) {
1113 delete []watchers;
1114 watchers = 0;
1115 lenWatchers = 0;
1116 } else {
1117 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
1118 if (!pwNew)
1119 return false;
1120 for (int j = 0; j < lenWatchers - 1; j++) {
1121 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
1122 }
1123 delete []watchers;
1124 watchers = pwNew;
1125 lenWatchers--;
1126 }
1127 return true;
1128 }
1129 }
1130 return false;
1131 }
1132
1133 void Document::NotifyModifyAttempt() {
1134 for (int i = 0; i < lenWatchers; i++) {
1135 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
1136 }
1137 }
1138
1139 void Document::NotifySavePoint(bool atSavePoint) {
1140 for (int i = 0; i < lenWatchers; i++) {
1141 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
1142 }
1143 }
1144
1145 void Document::NotifyModified(DocModification mh) {
1146 for (int i = 0; i < lenWatchers; i++) {
1147 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
1148 }
1149 }
1150
1151 bool Document::IsWordPartSeparator(char ch) {
1152 return ispunct(ch) && IsWordChar(ch);
1153 }
1154
1155 int Document::WordPartLeft(int pos) {
1156 if (pos > 0) {
1157 --pos;
1158 char startChar = cb.CharAt(pos);
1159 if (IsWordPartSeparator(startChar)) {
1160 while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {
1161 --pos;
1162 }
1163 }
1164 if (pos > 0) {
1165 startChar = cb.CharAt(pos);
1166 --pos;
1167 if (islower(startChar)) {
1168 while (pos > 0 && islower(cb.CharAt(pos)))
1169 --pos;
1170 if (!isupper(cb.CharAt(pos)) && !islower(cb.CharAt(pos)))
1171 ++pos;
1172 } else if (isupper(startChar)) {
1173 while (pos > 0 && isupper(cb.CharAt(pos)))
1174 --pos;
1175 if (!isupper(cb.CharAt(pos)))
1176 ++pos;
1177 } else if (isdigit(startChar)) {
1178 while (pos > 0 && isdigit(cb.CharAt(pos)))
1179 --pos;
1180 if (!isdigit(cb.CharAt(pos)))
1181 ++pos;
1182 } else if (ispunct(startChar)) {
1183 while (pos > 0 && ispunct(cb.CharAt(pos)))
1184 --pos;
1185 if (!ispunct(cb.CharAt(pos)))
1186 ++pos;
1187 } else if (isspacechar(startChar)) {
1188 while (pos > 0 && isspacechar(cb.CharAt(pos)))
1189 --pos;
1190 if (!isspacechar(cb.CharAt(pos)))
1191 ++pos;
1192 }
1193 }
1194 }
1195 return pos;
1196 }
1197
1198 int Document::WordPartRight(int pos) {
1199 char startChar = cb.CharAt(pos);
1200 int length = Length();
1201 if (IsWordPartSeparator(startChar)) {
1202 while (pos < length && IsWordPartSeparator(cb.CharAt(pos)))
1203 ++pos;
1204 startChar = cb.CharAt(pos);
1205 }
1206 if (islower(startChar)) {
1207 while (pos < length && islower(cb.CharAt(pos)))
1208 ++pos;
1209 } else if (isupper(startChar)) {
1210 if (islower(cb.CharAt(pos + 1))) {
1211 ++pos;
1212 while (pos < length && islower(cb.CharAt(pos)))
1213 ++pos;
1214 } else {
1215 while (pos < length && isupper(cb.CharAt(pos)))
1216 ++pos;
1217 }
1218 if (islower(cb.CharAt(pos)) && isupper(cb.CharAt(pos - 1)))
1219 --pos;
1220 } else if (isdigit(startChar)) {
1221 while (pos < length && isdigit(cb.CharAt(pos)))
1222 ++pos;
1223 } else if (ispunct(startChar)) {
1224 while (pos < length && ispunct(cb.CharAt(pos)))
1225 ++pos;
1226 } else if (isspacechar(startChar)) {
1227 while (pos < length && isspacechar(cb.CharAt(pos)))
1228 ++pos;
1229 }
1230 return pos;
1231 }