]> git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/Document.cxx
c883dd253f9a3bd1a5b65b6d0bd578e78fabefde
[wxWidgets.git] / contrib / src / stc / scintilla / src / Document.cxx
1 // Scintilla source code edit control
2 // Document.cxx - text document that handles notifications, DBCS, styling, words and end of line
3 // Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org>
4 // The License.txt file describes the conditions under which this software may be distributed.
5
6 #include <stdlib.h>
7 #include <string.h>
8 #include <stdio.h>
9 #include <ctype.h>
10
11 #include "Platform.h"
12
13 #include "Scintilla.h"
14 #include "SVector.h"
15 #include "CellBuffer.h"
16 #include "Document.h"
17
18 Document::Document() {
19 refCount = 0;
20 #ifdef unix
21 eolMode = SC_EOL_LF;
22 #else
23 eolMode = SC_EOL_CRLF;
24 #endif
25 dbcsCodePage = 0;
26 stylingBits = 5;
27 stylingBitsMask = 0x1F;
28 stylingPos = 0;
29 stylingMask = 0;
30 for (int ch = 0; ch < 256; ch++) {
31 wordchars[ch] = isalnum(ch) || ch == '_';
32 }
33 endStyled = 0;
34 enteredCount = 0;
35 enteredReadOnlyCount = 0;
36 tabInChars = 8;
37 indentInChars = 0;
38 useTabs = true;
39 watchers = 0;
40 lenWatchers = 0;
41 }
42
43 Document::~Document() {
44 for (int i = 0; i < lenWatchers; i++) {
45 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
46 }
47 delete []watchers;
48 watchers = 0;
49 lenWatchers = 0;
50 }
51
52 // Increase reference count and return its previous value.
53 int Document::AddRef() {
54 return refCount++;
55 }
56
57 // Decrease reference count and return its previous value.
58 // Delete the document if reference count reaches zero.
59 int Document::Release() {
60 int curRefCount = --refCount;
61 if (curRefCount == 0)
62 delete this;
63 return curRefCount;
64 }
65
66 void Document::SetSavePoint() {
67 cb.SetSavePoint();
68 NotifySavePoint(true);
69 }
70
71 int Document::AddMark(int line, int markerNum) {
72 int prev = cb.AddMark(line, markerNum);
73 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
74 NotifyModified(mh);
75 return prev;
76 }
77
78 void Document::DeleteMark(int line, int markerNum) {
79 cb.DeleteMark(line, markerNum);
80 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
81 NotifyModified(mh);
82 }
83
84 void Document::DeleteMarkFromHandle(int markerHandle) {
85 cb.DeleteMarkFromHandle(markerHandle);
86 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
87 NotifyModified(mh);
88 }
89
90 void Document::DeleteAllMarks(int markerNum) {
91 cb.DeleteAllMarks(markerNum);
92 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
93 NotifyModified(mh);
94 }
95
96 int Document::LineStart(int line) {
97 return cb.LineStart(line);
98 }
99
100 int Document::LineEnd(int line) {
101 if (line == LinesTotal() - 1) {
102 return LineStart(line + 1);
103 } else {
104 int position = LineStart(line + 1) - 1;
105 // When line terminator is CR+LF, may need to go back one more
106 if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
107 position--;
108 }
109 return position;
110 }
111 }
112
113 int Document::LineFromPosition(int pos) {
114 return cb.LineFromPosition(pos);
115 }
116
117 int Document::LineEndPosition(int position) {
118 return LineEnd(LineFromPosition(position));
119 }
120
121 int Document::VCHomePosition(int position) {
122 int line = LineFromPosition(position);
123 int startPosition = LineStart(line);
124 int endLine = LineStart(line + 1) - 1;
125 int startText = startPosition;
126 while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
127 startText++;
128 if (position == startText)
129 return startPosition;
130 else
131 return startText;
132 }
133
134 int Document::SetLevel(int line, int level) {
135 int prev = cb.SetLevel(line, level);
136 if (prev != level) {
137 DocModification mh(SC_MOD_CHANGEFOLD, LineStart(line), 0, 0, 0);
138 mh.line = line;
139 mh.foldLevelNow = level;
140 mh.foldLevelPrev = prev;
141 NotifyModified(mh);
142 }
143 return prev;
144 }
145
146 static bool IsSubordinate(int levelStart, int levelTry) {
147 if (levelTry & SC_FOLDLEVELWHITEFLAG)
148 return true;
149 else
150 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
151 }
152
153 int Document::GetLastChild(int lineParent, int level) {
154 if (level == -1)
155 level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
156 int maxLine = LinesTotal();
157 int lineMaxSubord = lineParent;
158 while (lineMaxSubord < maxLine-1) {
159 EnsureStyledTo(LineStart(lineMaxSubord+2));
160 if (!IsSubordinate(level, GetLevel(lineMaxSubord+1)))
161 break;
162 lineMaxSubord++;
163 }
164 if (lineMaxSubord > lineParent) {
165 if (level > (GetLevel(lineMaxSubord+1) & SC_FOLDLEVELNUMBERMASK)) {
166 // Have chewed up some whitespace that belongs to a parent so seek back
167 if ((lineMaxSubord > lineParent) && (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG)) {
168 lineMaxSubord--;
169 }
170 }
171 }
172 return lineMaxSubord;
173 }
174
175 int Document::GetFoldParent(int line) {
176 int level = GetLevel(line);
177 int lineLook = line-1;
178 while ((lineLook > 0) && (
179 (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
180 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
181 ) {
182 lineLook--;
183 }
184 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
185 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
186 return lineLook;
187 } else {
188 return -1;
189 }
190 }
191
192 int Document::ClampPositionIntoDocument(int pos) {
193 return Platform::Clamp(pos, 0, Length());
194 }
195
196 bool Document::IsCrLf(int pos) {
197 if (pos < 0)
198 return false;
199 if (pos >= (Length() - 1))
200 return false;
201 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
202 }
203
204 #if PLAT_WIN
205 bool Document::IsDBCS(int pos) {
206 if (dbcsCodePage) {
207 if (SC_CP_UTF8 == dbcsCodePage) {
208 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
209 return ch >= 0x80;
210 } else {
211 // Anchor DBCS calculations at start of line because start of line can
212 // not be a DBCS trail byte.
213 int startLine = pos;
214 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
215 startLine--;
216 while (startLine <= pos) {
217 if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) {
218 startLine++;
219 if (startLine >= pos)
220 return true;
221 }
222 startLine++;
223 }
224 }
225 }
226 return false;
227 }
228 #else
229 // PLAT_GTK or PLAT_WX
230 // TODO: support DBCS under GTK+ and WX
231 bool Document::IsDBCS(int) {
232 return false;
233 }
234 #endif
235
236 int Document::LenChar(int pos) {
237 if (IsCrLf(pos)) {
238 return 2;
239 } else if (SC_CP_UTF8 == dbcsCodePage) {
240 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
241 if (ch < 0x80)
242 return 1;
243 int len = 2;
244 if (ch >= (0x80+0x40+0x20))
245 len = 3;
246 int lengthDoc = Length();
247 if ((pos + len) > lengthDoc)
248 return lengthDoc-pos;
249 else
250 return len;
251 } else if (IsDBCS(pos)) {
252 return 2;
253 } else {
254 return 1;
255 }
256 }
257
258 // Normalise a position so that it is not halfway through a two byte character.
259 // This can occur in two situations -
260 // When lines are terminated with \r\n pairs which should be treated as one character.
261 // When displaying DBCS text such as Japanese.
262 // If moving, move the position in the indicated direction.
263 int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
264 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
265 // If out of range, just return value - should be fixed up after
266 if (pos < 0)
267 return pos;
268 if (pos > Length())
269 return pos;
270
271 // Position 0 and Length() can not be between any two characters
272 if (pos == 0)
273 return pos;
274 if (pos == Length())
275 return pos;
276
277 // assert pos > 0 && pos < Length()
278 if (checkLineEnd && IsCrLf(pos - 1)) {
279 if (moveDir > 0)
280 return pos + 1;
281 else
282 return pos - 1;
283 }
284
285 // Not between CR and LF
286
287 #if PLAT_WIN
288 if (dbcsCodePage) {
289 if (SC_CP_UTF8 == dbcsCodePage) {
290 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
291 while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
292 // ch is a trail byte
293 if (moveDir > 0)
294 pos++;
295 else
296 pos--;
297 ch = static_cast<unsigned char>(cb.CharAt(pos));
298 }
299 } else {
300 // Anchor DBCS calculations at start of line because start of line can
301 // not be a DBCS trail byte.
302 int startLine = pos;
303 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
304 startLine--;
305 bool atLeadByte = false;
306 while (startLine < pos) {
307 if (atLeadByte)
308 atLeadByte = false;
309 else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine)))
310 atLeadByte = true;
311 else
312 atLeadByte = false;
313 startLine++;
314 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
315 }
316
317 if (atLeadByte) {
318 // Position is between a lead byte and a trail byte
319 if (moveDir > 0)
320 return pos + 1;
321 else
322 return pos - 1;
323 }
324 }
325 }
326 #endif
327
328 return pos;
329 }
330
331 void Document::ModifiedAt(int pos) {
332 if (endStyled > pos)
333 endStyled = pos;
334 }
335
336 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
337 // SetStyleAt does not change the persistent state of a document
338
339 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
340 void Document::DeleteChars(int pos, int len) {
341 if ((pos + len) > Length())
342 return;
343 if (cb.IsReadOnly() && enteredReadOnlyCount==0) {
344 enteredReadOnlyCount++;
345 NotifyModifyAttempt();
346 enteredReadOnlyCount--;
347 }
348 if (enteredCount == 0) {
349 enteredCount++;
350 if (!cb.IsReadOnly()) {
351 NotifyModified(
352 DocModification(
353 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
354 pos, len,
355 0, 0));
356 int prevLinesTotal = LinesTotal();
357 bool startSavePoint = cb.IsSavePoint();
358 const char *text = cb.DeleteChars(pos*2, len * 2);
359 if (startSavePoint && cb.IsCollectingUndo())
360 NotifySavePoint(!startSavePoint);
361 ModifiedAt(pos);
362 NotifyModified(
363 DocModification(
364 SC_MOD_DELETETEXT | SC_PERFORMED_USER,
365 pos, len,
366 LinesTotal() - prevLinesTotal, text));
367 }
368 enteredCount--;
369 }
370 }
371
372 void Document::InsertStyledString(int position, char *s, int insertLength) {
373 if (cb.IsReadOnly() && enteredReadOnlyCount==0) {
374 enteredReadOnlyCount++;
375 NotifyModifyAttempt();
376 enteredReadOnlyCount--;
377 }
378 if (enteredCount == 0) {
379 enteredCount++;
380 if (!cb.IsReadOnly()) {
381 NotifyModified(
382 DocModification(
383 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
384 position / 2, insertLength / 2,
385 0, 0));
386 int prevLinesTotal = LinesTotal();
387 bool startSavePoint = cb.IsSavePoint();
388 const char *text = cb.InsertString(position, s, insertLength);
389 if (startSavePoint && cb.IsCollectingUndo())
390 NotifySavePoint(!startSavePoint);
391 ModifiedAt(position / 2);
392 NotifyModified(
393 DocModification(
394 SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
395 position / 2, insertLength / 2,
396 LinesTotal() - prevLinesTotal, text));
397 }
398 enteredCount--;
399 }
400 }
401
402 int Document::Undo() {
403 int newPos = 0;
404 if (enteredCount == 0) {
405 enteredCount++;
406 bool startSavePoint = cb.IsSavePoint();
407 int steps = cb.StartUndo();
408 //Platform::DebugPrintf("Steps=%d\n", steps);
409 for (int step=0; step<steps; step++) {
410 int prevLinesTotal = LinesTotal();
411 const Action &action = cb.GetUndoStep();
412 if (action.at == removeAction) {
413 NotifyModified(DocModification(
414 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
415 } else {
416 NotifyModified(DocModification(
417 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
418 }
419 cb.PerformUndoStep();
420 int cellPosition = action.position / 2;
421 ModifiedAt(cellPosition);
422 newPos = cellPosition;
423
424 int modFlags = SC_PERFORMED_UNDO;
425 // With undo, an insertion action becomes a deletion notification
426 if (action.at == removeAction) {
427 newPos += action.lenData;
428 modFlags |= SC_MOD_INSERTTEXT;
429 } else {
430 modFlags |= SC_MOD_DELETETEXT;
431 }
432 if (step == steps-1)
433 modFlags |= SC_LASTSTEPINUNDOREDO;
434 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
435 LinesTotal() - prevLinesTotal, action.data));
436 }
437
438 bool endSavePoint = cb.IsSavePoint();
439 if (startSavePoint != endSavePoint)
440 NotifySavePoint(endSavePoint);
441 enteredCount--;
442 }
443 return newPos;
444 }
445
446 int Document::Redo() {
447 int newPos = 0;
448 if (enteredCount == 0) {
449 enteredCount++;
450 bool startSavePoint = cb.IsSavePoint();
451 int steps = cb.StartRedo();
452 for (int step=0; step<steps; step++) {
453 int prevLinesTotal = LinesTotal();
454 const Action &action = cb.GetRedoStep();
455 if (action.at == insertAction) {
456 NotifyModified(DocModification(
457 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
458 } else {
459 NotifyModified(DocModification(
460 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
461 }
462 cb.PerformRedoStep();
463 ModifiedAt(action.position / 2);
464 newPos = action.position / 2;
465
466 int modFlags = SC_PERFORMED_REDO;
467 if (action.at == insertAction) {
468 newPos += action.lenData;
469 modFlags |= SC_MOD_INSERTTEXT;
470 } else {
471 modFlags |= SC_MOD_DELETETEXT;
472 }
473 if (step == steps-1)
474 modFlags |= SC_LASTSTEPINUNDOREDO;
475 NotifyModified(
476 DocModification(modFlags, action.position / 2, action.lenData,
477 LinesTotal() - prevLinesTotal, action.data));
478 }
479
480 bool endSavePoint = cb.IsSavePoint();
481 if (startSavePoint != endSavePoint)
482 NotifySavePoint(endSavePoint);
483 enteredCount--;
484 }
485 return newPos;
486 }
487
488 void Document::InsertChar(int pos, char ch) {
489 char chs[2];
490 chs[0] = ch;
491 chs[1] = 0;
492 InsertStyledString(pos*2, chs, 2);
493 }
494
495 // Insert a null terminated string
496 void Document::InsertString(int position, const char *s) {
497 InsertString(position, s, strlen(s));
498 }
499
500 // Insert a string with a length
501 void Document::InsertString(int position, const char *s, int insertLength) {
502 char *sWithStyle = new char[insertLength * 2];
503 if (sWithStyle) {
504 for (int i = 0; i < insertLength; i++) {
505 sWithStyle[i*2] = s[i];
506 sWithStyle[i*2 + 1] = 0;
507 }
508 InsertStyledString(position*2, sWithStyle, insertLength*2);
509 delete []sWithStyle;
510 }
511 }
512
513 void Document::ChangeChar(int pos, char ch) {
514 DeleteChars(pos, 1);
515 InsertChar(pos, ch);
516 }
517
518 void Document::DelChar(int pos) {
519 DeleteChars(pos, LenChar(pos));
520 }
521
522 int Document::DelCharBack(int pos) {
523 if (pos <= 0) {
524 return pos;
525 } else if (IsCrLf(pos - 2)) {
526 DeleteChars(pos - 2, 2);
527 return pos - 2;
528 } else if (SC_CP_UTF8 == dbcsCodePage) {
529 int startChar = MovePositionOutsideChar(pos-1, -1, false);
530 DeleteChars(startChar, pos - startChar);
531 return startChar;
532 } else if (IsDBCS(pos - 1)) {
533 DeleteChars(pos - 2, 2);
534 return pos - 2;
535 } else {
536 DeleteChars(pos - 1, 1);
537 return pos - 1;
538 }
539 }
540
541 static bool isindentchar(char ch) {
542 return (ch == ' ') || (ch == '\t');
543 }
544
545 static int NextTab(int pos, int tabSize) {
546 return ((pos / tabSize) + 1) * tabSize;
547 }
548
549 static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
550 length--; // ensure space for \0
551 if (!insertSpaces) {
552 while ((indent >= tabSize) && (length > 0)) {
553 *linebuf++ = '\t';
554 indent -= tabSize;
555 length--;
556 }
557 }
558 while ((indent > 0) && (length > 0)) {
559 *linebuf++ = ' ';
560 indent--;
561 length--;
562 }
563 *linebuf = '\0';
564 }
565
566 int Document::GetLineIndentation(int line) {
567 int indent = 0;
568 if ((line >= 0) && (line < LinesTotal())) {
569 int lineStart = LineStart(line);
570 int length = Length();
571 for (int i=lineStart;i<length;i++) {
572 char ch = cb.CharAt(i);
573 if (ch == ' ')
574 indent++;
575 else if (ch == '\t')
576 indent = NextTab(indent, tabInChars);
577 else
578 return indent;
579 }
580 }
581 return indent;
582 }
583
584 void Document::SetLineIndentation(int line, int indent) {
585 int indentOfLine = GetLineIndentation(line);
586 if (indent < 0)
587 indent = 0;
588 if (indent != indentOfLine) {
589 char linebuf[1000];
590 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
591 int thisLineStart = LineStart(line);
592 int indentPos = GetLineIndentPosition(line);
593 DeleteChars(thisLineStart, indentPos - thisLineStart);
594 InsertString(thisLineStart, linebuf);
595 }
596 }
597
598 int Document::GetLineIndentPosition(int line) {
599 if (line < 0)
600 return 0;
601 int pos = LineStart(line);
602 int length = Length();
603 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
604 pos++;
605 }
606 return pos;
607 }
608
609 int Document::GetColumn(int pos) {
610 int column = 0;
611 int line = LineFromPosition(pos);
612 if ((line >= 0) && (line < LinesTotal())) {
613 for (int i=LineStart(line);i<pos;i++) {
614 char ch = cb.CharAt(i);
615 if (ch == '\t')
616 column = NextTab(column, tabInChars);
617 else if (ch == '\r')
618 return column;
619 else if (ch == '\n')
620 return column;
621 else
622 column++;
623 }
624 }
625 return column;
626 }
627
628 void Document::Indent(bool forwards, int lineBottom, int lineTop) {
629 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
630 for (int line = lineBottom; line >= lineTop; line--) {
631 int indentOfLine = GetLineIndentation(line);
632 if (forwards)
633 SetLineIndentation(line, indentOfLine + IndentSize());
634 else
635 SetLineIndentation(line, indentOfLine - IndentSize());
636 }
637 }
638
639 void Document::ConvertLineEnds(int eolModeSet) {
640 BeginUndoAction();
641 for (int pos = 0; pos < Length(); pos++) {
642 if (cb.CharAt(pos) == '\r') {
643 if (cb.CharAt(pos+1) == '\n') {
644 if (eolModeSet != SC_EOL_CRLF) {
645 DeleteChars(pos, 2);
646 if (eolModeSet == SC_EOL_CR)
647 InsertString(pos, "\r", 1);
648 else
649 InsertString(pos, "\n", 1);
650 } else {
651 pos++;
652 }
653 } else {
654 if (eolModeSet != SC_EOL_CR) {
655 DeleteChars(pos, 1);
656 if (eolModeSet == SC_EOL_CRLF) {
657 InsertString(pos, "\r\n", 2);
658 pos++;
659 } else {
660 InsertString(pos, "\n", 1);
661 }
662 }
663 }
664 } else if (cb.CharAt(pos) == '\n') {
665 if (eolModeSet != SC_EOL_LF) {
666 DeleteChars(pos, 1);
667 if (eolModeSet == SC_EOL_CRLF) {
668 InsertString(pos, "\r\n", 2);
669 pos++;
670 } else {
671 InsertString(pos, "\r", 1);
672 }
673 }
674 }
675 }
676 EndUndoAction();
677 }
678
679 bool Document::IsWordChar(unsigned char ch) {
680 if ((SC_CP_UTF8 == dbcsCodePage) && (ch >0x80))
681 return true;
682 return wordchars[ch];
683 }
684
685 int Document::ExtendWordSelect(int pos, int delta) {
686 if (delta < 0) {
687 while (pos > 0 && IsWordChar(cb.CharAt(pos - 1)))
688 pos--;
689 } else {
690 while (pos < (Length()) && IsWordChar(cb.CharAt(pos)))
691 pos++;
692 }
693 return pos;
694 }
695
696 int Document::NextWordStart(int pos, int delta) {
697 if (delta < 0) {
698 while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t'))
699 pos--;
700 if (isspace(cb.CharAt(pos - 1))) { // Back up to previous line
701 while (pos > 0 && isspace(cb.CharAt(pos - 1)))
702 pos--;
703 } else {
704 bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1));
705 while (pos > 0 && !isspace(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1))))
706 pos--;
707 }
708 } else {
709 bool startAtWordChar = IsWordChar(cb.CharAt(pos));
710 while (pos < (Length()) && isspace(cb.CharAt(pos)))
711 pos++;
712 while (pos < (Length()) && !isspace(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos))))
713 pos++;
714 while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t'))
715 pos++;
716 }
717 return pos;
718 }
719
720 bool Document::IsWordStartAt(int pos) {
721 if (pos > 0) {
722 return !IsWordChar(CharAt(pos - 1));
723 }
724 return true;
725 }
726
727 bool Document::IsWordEndAt(int pos) {
728 if (pos < Length() - 1) {
729 return !IsWordChar(CharAt(pos));
730 }
731 return true;
732 }
733
734 bool Document::IsWordAt(int start, int end) {
735 return IsWordStartAt(start) && IsWordEndAt(end);
736 }
737
738 // Find text in document, supporting both forward and backward
739 // searches (just pass minPos > maxPos to do a backward search)
740 // Has not been tested with backwards DBCS searches yet.
741 long Document::FindText(int minPos, int maxPos, const char *s,
742 bool caseSensitive, bool word, bool wordStart) {
743 bool forward = minPos <= maxPos;
744 int increment = forward ? 1 : -1;
745
746 // Range endpoints should not be inside DBCS characters, but just in case, move them.
747 int startPos = MovePositionOutsideChar(minPos, increment, false);
748 int endPos = MovePositionOutsideChar(maxPos, increment, false);
749
750 // Compute actual search ranges needed
751 int lengthFind = strlen(s);
752 int endSearch = endPos;
753 if (startPos <= endPos) {
754 endSearch = endPos - lengthFind + 1;
755 }
756 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
757 char firstChar = s[0];
758 if (!caseSensitive)
759 firstChar = static_cast<char>(toupper(firstChar));
760 int pos = startPos;
761 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
762 char ch = CharAt(pos);
763 if (caseSensitive) {
764 if (ch == firstChar) {
765 bool found = true;
766 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
767 ch = CharAt(pos + posMatch);
768 if (ch != s[posMatch])
769 found = false;
770 }
771 if (found) {
772 if ((!word && !wordStart) ||
773 word && IsWordAt(pos, pos + lengthFind) ||
774 wordStart && IsWordStartAt(pos))
775 return pos;
776 }
777 }
778 } else {
779 if (toupper(ch) == firstChar) {
780 bool found = true;
781 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
782 ch = CharAt(pos + posMatch);
783 if (toupper(ch) != toupper(s[posMatch]))
784 found = false;
785 }
786 if (found) {
787 if (!(word && wordStart) ||
788 word && IsWordAt(pos, pos + lengthFind) ||
789 wordStart && IsWordStartAt(pos))
790 return pos;
791 }
792 }
793 }
794 pos += increment;
795 if (dbcsCodePage) {
796 // Ensure trying to match from start of character
797 pos = MovePositionOutsideChar(pos, increment, false);
798 }
799 }
800 //Platform::DebugPrintf("Not found\n");
801 return - 1;
802 }
803
804 int Document::LinesTotal() {
805 return cb.Lines();
806 }
807
808 void Document::ChangeCase(Range r, bool makeUpperCase) {
809 for (int pos=r.start; pos<r.end; pos++) {
810 char ch = CharAt(pos);
811 if (dbcsCodePage && IsDBCS(pos)) {
812 pos += LenChar(pos);
813 } else {
814 if (makeUpperCase) {
815 if (islower(ch)) {
816 ChangeChar(pos, static_cast<char>(toupper(ch)));
817 }
818 } else {
819 if (isupper(ch)) {
820 ChangeChar(pos, static_cast<char>(tolower(ch)));
821 }
822 }
823 }
824 }
825 }
826
827 void Document::SetWordChars(unsigned char *chars) {
828 int ch;
829 for (ch = 0; ch < 256; ch++) {
830 wordchars[ch] = false;
831 }
832 if (chars) {
833 while (*chars) {
834 wordchars[*chars] = true;
835 chars++;
836 }
837 } else {
838 for (ch = 0; ch < 256; ch++) {
839 wordchars[ch] = isalnum(ch) || ch == '_';
840 }
841 }
842 }
843
844 void Document::SetStylingBits(int bits) {
845 stylingBits = bits;
846 stylingBitsMask = 0;
847 for (int bit=0; bit<stylingBits; bit++) {
848 stylingBitsMask <<= 1;
849 stylingBitsMask |= 1;
850 }
851 }
852
853 void Document::StartStyling(int position, char mask) {
854 stylingPos = position;
855 stylingMask = mask;
856 }
857
858 void Document::SetStyleFor(int length, char style) {
859 if (enteredCount == 0) {
860 enteredCount++;
861 int prevEndStyled = endStyled;
862 if (cb.SetStyleFor(stylingPos, length, style, stylingMask)) {
863 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
864 prevEndStyled, length);
865 NotifyModified(mh);
866 }
867 stylingPos += length;
868 endStyled = stylingPos;
869 enteredCount--;
870 }
871 }
872
873 void Document::SetStyles(int length, char *styles) {
874 if (enteredCount == 0) {
875 enteredCount++;
876 int prevEndStyled = endStyled;
877 bool didChange = false;
878 for (int iPos = 0; iPos < length; iPos++, stylingPos++) {
879 if (cb.SetStyleAt(stylingPos, styles[iPos], stylingMask)) {
880 didChange = true;
881 }
882 }
883 endStyled = stylingPos;
884 if (didChange) {
885 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
886 prevEndStyled, endStyled - prevEndStyled);
887 NotifyModified(mh);
888 }
889 enteredCount--;
890 }
891 }
892
893 bool Document::EnsureStyledTo(int pos) {
894 // Ask the watchers to style, and stop as soon as one responds.
895 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++)
896 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
897 return pos <= GetEndStyled();
898 }
899
900 bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
901 for (int i = 0; i < lenWatchers; i++) {
902 if ((watchers[i].watcher == watcher) &&
903 (watchers[i].userData == userData))
904 return false;
905 }
906 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
907 if (!pwNew)
908 return false;
909 for (int j = 0; j < lenWatchers; j++)
910 pwNew[j] = watchers[j];
911 pwNew[lenWatchers].watcher = watcher;
912 pwNew[lenWatchers].userData = userData;
913 delete []watchers;
914 watchers = pwNew;
915 lenWatchers++;
916 return true;
917 }
918
919 bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
920 for (int i = 0; i < lenWatchers; i++) {
921 if ((watchers[i].watcher == watcher) &&
922 (watchers[i].userData == userData)) {
923 if (lenWatchers == 1) {
924 delete []watchers;
925 watchers = 0;
926 lenWatchers = 0;
927 } else {
928 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
929 if (!pwNew)
930 return false;
931 for (int j = 0; j < lenWatchers - 1; j++) {
932 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
933 }
934 delete []watchers;
935 watchers = pwNew;
936 lenWatchers--;
937 }
938 return true;
939 }
940 }
941 return false;
942 }
943
944 void Document::NotifyModifyAttempt() {
945 for (int i = 0; i < lenWatchers; i++) {
946 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
947 }
948 }
949
950 void Document::NotifySavePoint(bool atSavePoint) {
951 for (int i = 0; i < lenWatchers; i++) {
952 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
953 }
954 }
955
956 void Document::NotifyModified(DocModification mh) {
957 for (int i = 0; i < lenWatchers; i++) {
958 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
959 }
960 }