]> git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
6614a7fc98083f8d7c9155992013da5e96c89568
[wxWidgets.git] / 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 provius 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 bool Document::IsDBCS(int pos) {
205 #if PLAT_WIN
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 #else
228 return false;
229 #endif
230 }
231
232 int Document::LenChar(int pos) {
233 if (IsCrLf(pos)) {
234 return 2;
235 } else if (SC_CP_UTF8 == dbcsCodePage) {
236 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
237 if (ch < 0x80)
238 return 1;
239 int len = 2;
240 if (ch >= (0x80+0x40+0x20))
241 len = 3;
242 int lengthDoc = Length();
243 if ((pos + len) > lengthDoc)
244 return lengthDoc-pos;
245 else
246 return len;
247 } else if (IsDBCS(pos)) {
248 return 2;
249 } else {
250 return 1;
251 }
252 }
253
254 // Normalise a position so that it is not halfway through a two byte character.
255 // This can occur in two situations -
256 // When lines are terminated with \r\n pairs which should be treated as one character.
257 // When displaying DBCS text such as Japanese.
258 // If moving, move the position in the indicated direction.
259 int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
260 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
261 // If out of range, just return value - should be fixed up after
262 if (pos < 0)
263 return pos;
264 if (pos > Length())
265 return pos;
266
267 // Position 0 and Length() can not be between any two characters
268 if (pos == 0)
269 return pos;
270 if (pos == Length())
271 return pos;
272
273 // assert pos > 0 && pos < Length()
274 if (checkLineEnd && IsCrLf(pos - 1)) {
275 if (moveDir > 0)
276 return pos + 1;
277 else
278 return pos - 1;
279 }
280
281 // Not between CR and LF
282
283 #if PLAT_WIN
284 if (dbcsCodePage) {
285 if (SC_CP_UTF8 == dbcsCodePage) {
286 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
287 while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
288 // ch is a trail byte
289 if (moveDir > 0)
290 pos++;
291 else
292 pos--;
293 ch = static_cast<unsigned char>(cb.CharAt(pos));
294 }
295 } else {
296 // Anchor DBCS calculations at start of line because start of line can
297 // not be a DBCS trail byte.
298 int startLine = pos;
299 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
300 startLine--;
301 bool atLeadByte = false;
302 while (startLine < pos) {
303 if (atLeadByte)
304 atLeadByte = false;
305 else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine)))
306 atLeadByte = true;
307 else
308 atLeadByte = false;
309 startLine++;
310 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
311 }
312
313 if (atLeadByte) {
314 // Position is between a lead byte and a trail byte
315 if (moveDir > 0)
316 return pos + 1;
317 else
318 return pos - 1;
319 }
320 }
321 }
322 #endif
323
324 return pos;
325 }
326
327 void Document::ModifiedAt(int pos) {
328 if (endStyled > pos)
329 endStyled = pos;
330 }
331
332 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
333 // SetStyleAt does not change the persistent state of a document
334
335 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
336 void Document::DeleteChars(int pos, int len) {
337 if (cb.IsReadOnly() && enteredReadOnlyCount==0) {
338 enteredReadOnlyCount++;
339 NotifyModifyAttempt();
340 enteredReadOnlyCount--;
341 }
342 if (enteredCount == 0) {
343 enteredCount++;
344 if (!cb.IsReadOnly()) {
345 NotifyModified(
346 DocModification(
347 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
348 pos, len,
349 0, 0));
350 int prevLinesTotal = LinesTotal();
351 bool startSavePoint = cb.IsSavePoint();
352 const char *text = cb.DeleteChars(pos*2, len * 2);
353 if (startSavePoint && cb.IsCollectingUndo())
354 NotifySavePoint(!startSavePoint);
355 ModifiedAt(pos);
356 NotifyModified(
357 DocModification(
358 SC_MOD_DELETETEXT | SC_PERFORMED_USER,
359 pos, len,
360 LinesTotal() - prevLinesTotal, text));
361 }
362 enteredCount--;
363 }
364 }
365
366 void Document::InsertStyledString(int position, char *s, int insertLength) {
367 if (cb.IsReadOnly() && enteredReadOnlyCount==0) {
368 enteredReadOnlyCount++;
369 NotifyModifyAttempt();
370 enteredReadOnlyCount--;
371 }
372 if (enteredCount == 0) {
373 enteredCount++;
374 if (!cb.IsReadOnly()) {
375 NotifyModified(
376 DocModification(
377 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
378 position / 2, insertLength / 2,
379 0, 0));
380 int prevLinesTotal = LinesTotal();
381 bool startSavePoint = cb.IsSavePoint();
382 const char *text = cb.InsertString(position, s, insertLength);
383 if (startSavePoint && cb.IsCollectingUndo())
384 NotifySavePoint(!startSavePoint);
385 ModifiedAt(position / 2);
386 NotifyModified(
387 DocModification(
388 SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
389 position / 2, insertLength / 2,
390 LinesTotal() - prevLinesTotal, text));
391 }
392 enteredCount--;
393 }
394 }
395
396 int Document::Undo() {
397 int newPos = 0;
398 if (enteredCount == 0) {
399 enteredCount++;
400 bool startSavePoint = cb.IsSavePoint();
401 int steps = cb.StartUndo();
402 //Platform::DebugPrintf("Steps=%d\n", steps);
403 for (int step=0; step<steps; step++) {
404 int prevLinesTotal = LinesTotal();
405 const Action &action = cb.GetUndoStep();
406 if (action.at == removeAction) {
407 NotifyModified(DocModification(
408 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
409 } else {
410 NotifyModified(DocModification(
411 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
412 }
413 cb.PerformUndoStep();
414 int cellPosition = action.position / 2;
415 ModifiedAt(cellPosition);
416 newPos = cellPosition;
417
418 int modFlags = SC_PERFORMED_UNDO;
419 // With undo, an insertion action becomes a deletion notification
420 if (action.at == removeAction) {
421 newPos += action.lenData;
422 modFlags |= SC_MOD_INSERTTEXT;
423 } else {
424 modFlags |= SC_MOD_DELETETEXT;
425 }
426 if (step == steps-1)
427 modFlags |= SC_LASTSTEPINUNDOREDO;
428 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
429 LinesTotal() - prevLinesTotal, action.data));
430 }
431
432 bool endSavePoint = cb.IsSavePoint();
433 if (startSavePoint != endSavePoint)
434 NotifySavePoint(endSavePoint);
435 enteredCount--;
436 }
437 return newPos;
438 }
439
440 int Document::Redo() {
441 int newPos = 0;
442 if (enteredCount == 0) {
443 enteredCount++;
444 bool startSavePoint = cb.IsSavePoint();
445 int steps = cb.StartRedo();
446 for (int step=0; step<steps; step++) {
447 int prevLinesTotal = LinesTotal();
448 const Action &action = cb.GetRedoStep();
449 if (action.at == insertAction) {
450 NotifyModified(DocModification(
451 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
452 } else {
453 NotifyModified(DocModification(
454 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
455 }
456 cb.PerformRedoStep();
457 ModifiedAt(action.position / 2);
458 newPos = action.position / 2;
459
460 int modFlags = SC_PERFORMED_REDO;
461 if (action.at == insertAction) {
462 newPos += action.lenData;
463 modFlags |= SC_MOD_INSERTTEXT;
464 } else {
465 modFlags |= SC_MOD_DELETETEXT;
466 }
467 if (step == steps-1)
468 modFlags |= SC_LASTSTEPINUNDOREDO;
469 NotifyModified(
470 DocModification(modFlags, action.position / 2, action.lenData,
471 LinesTotal() - prevLinesTotal, action.data));
472 }
473
474 bool endSavePoint = cb.IsSavePoint();
475 if (startSavePoint != endSavePoint)
476 NotifySavePoint(endSavePoint);
477 enteredCount--;
478 }
479 return newPos;
480 }
481
482 void Document::InsertChar(int pos, char ch) {
483 char chs[2];
484 chs[0] = ch;
485 chs[1] = 0;
486 InsertStyledString(pos*2, chs, 2);
487 }
488
489 // Insert a null terminated string
490 void Document::InsertString(int position, const char *s) {
491 InsertString(position, s, strlen(s));
492 }
493
494 // Insert a string with a length
495 void Document::InsertString(int position, const char *s, int insertLength) {
496 char *sWithStyle = new char[insertLength * 2];
497 if (sWithStyle) {
498 for (int i = 0; i < insertLength; i++) {
499 sWithStyle[i*2] = s[i];
500 sWithStyle[i*2 + 1] = 0;
501 }
502 InsertStyledString(position*2, sWithStyle, insertLength*2);
503 delete []sWithStyle;
504 }
505 }
506
507 void Document::ChangeChar(int pos, char ch) {
508 DeleteChars(pos, 1);
509 InsertChar(pos, ch);
510 }
511
512 void Document::DelChar(int pos) {
513 DeleteChars(pos, LenChar(pos));
514 }
515
516 int Document::DelCharBack(int pos) {
517 if (pos <= 0) {
518 return pos;
519 } else if (IsCrLf(pos - 2)) {
520 DeleteChars(pos - 2, 2);
521 return pos - 2;
522 } else if (SC_CP_UTF8 == dbcsCodePage) {
523 int startChar = MovePositionOutsideChar(pos-1, -1, false);
524 DeleteChars(startChar, pos - startChar);
525 return startChar;
526 } else if (IsDBCS(pos - 1)) {
527 DeleteChars(pos - 2, 2);
528 return pos - 2;
529 } else {
530 DeleteChars(pos - 1, 1);
531 return pos - 1;
532 }
533 }
534
535 static bool isindentchar(char ch) {
536 return (ch == ' ') || (ch == '\t');
537 }
538
539 static int NextTab(int pos, int tabSize) {
540 return ((pos / tabSize) + 1) * tabSize;
541 }
542
543 static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
544 length--; // ensure space for \0
545 if (!insertSpaces) {
546 while ((indent >= tabSize) && (length > 0)) {
547 *linebuf++ = '\t';
548 indent -= tabSize;
549 length--;
550 }
551 }
552 while ((indent > 0) && (length > 0)) {
553 *linebuf++ = ' ';
554 indent--;
555 length--;
556 }
557 *linebuf = '\0';
558 }
559
560 int Document::GetLineIndentation(int line) {
561 int indent = 0;
562 if ((line >= 0) && (line < LinesTotal())) {
563 int lineStart = LineStart(line);
564 int length = Length();
565 for (int i=lineStart;i<length;i++) {
566 char ch = cb.CharAt(i);
567 if (ch == ' ')
568 indent++;
569 else if (ch == '\t')
570 indent = NextTab(indent, tabInChars);
571 else
572 return indent;
573 }
574 }
575 return indent;
576 }
577
578 void Document::SetLineIndentation(int line, int indent) {
579 int indentOfLine = GetLineIndentation(line);
580 if (indent < 0)
581 indent = 0;
582 if (indent != indentOfLine) {
583 char linebuf[1000];
584 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
585 int thisLineStart = LineStart(line);
586 int indentPos = GetLineIndentPosition(line);
587 DeleteChars(thisLineStart, indentPos - thisLineStart);
588 InsertString(thisLineStart, linebuf);
589 }
590 }
591
592 int Document::GetLineIndentPosition(int line) {
593 int pos = LineStart(line);
594 int length = Length();
595 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
596 pos++;
597 }
598 return pos;
599 }
600
601 void Document::Indent(bool forwards, int lineBottom, int lineTop) {
602 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
603 for (int line = lineBottom; line >= lineTop; line--) {
604 int indentOfLine = GetLineIndentation(line);
605 if (forwards)
606 SetLineIndentation(line, indentOfLine + IndentSize());
607 else
608 SetLineIndentation(line, indentOfLine - IndentSize());
609 }
610 }
611
612 void Document::ConvertLineEnds(int eolModeSet) {
613 BeginUndoAction();
614 for (int pos = 0; pos < Length(); pos++) {
615 if (cb.CharAt(pos) == '\r') {
616 if (cb.CharAt(pos+1) == '\n') {
617 if (eolModeSet != SC_EOL_CRLF) {
618 DeleteChars(pos, 2);
619 if (eolModeSet == SC_EOL_CR)
620 InsertString(pos, "\r", 1);
621 else
622 InsertString(pos, "\n", 1);
623 } else {
624 pos++;
625 }
626 } else {
627 if (eolModeSet != SC_EOL_CR) {
628 DeleteChars(pos, 1);
629 if (eolModeSet == SC_EOL_CRLF) {
630 InsertString(pos, "\r\n", 2);
631 pos++;
632 } else {
633 InsertString(pos, "\n", 1);
634 }
635 }
636 }
637 } else if (cb.CharAt(pos) == '\n') {
638 if (eolModeSet != SC_EOL_LF) {
639 DeleteChars(pos, 1);
640 if (eolModeSet == SC_EOL_CRLF) {
641 InsertString(pos, "\r\n", 2);
642 pos++;
643 } else {
644 InsertString(pos, "\r", 1);
645 }
646 }
647 }
648 }
649 EndUndoAction();
650 }
651
652 bool Document::IsWordChar(unsigned char ch) {
653 if ((SC_CP_UTF8 == dbcsCodePage) && (ch >0x80))
654 return true;
655 return wordchars[ch];
656 }
657
658 int Document::ExtendWordSelect(int pos, int delta) {
659 if (delta < 0) {
660 while (pos > 0 && IsWordChar(cb.CharAt(pos - 1)))
661 pos--;
662 } else {
663 while (pos < (Length()) && IsWordChar(cb.CharAt(pos)))
664 pos++;
665 }
666 return pos;
667 }
668
669 int Document::NextWordStart(int pos, int delta) {
670 if (delta < 0) {
671 while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t'))
672 pos--;
673 if (isspace(cb.CharAt(pos - 1))) { // Back up to previous line
674 while (pos > 0 && isspace(cb.CharAt(pos - 1)))
675 pos--;
676 } else {
677 bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1));
678 while (pos > 0 && !isspace(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1))))
679 pos--;
680 }
681 } else {
682 bool startAtWordChar = IsWordChar(cb.CharAt(pos));
683 while (pos < (Length()) && isspace(cb.CharAt(pos)))
684 pos++;
685 while (pos < (Length()) && !isspace(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos))))
686 pos++;
687 while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t'))
688 pos++;
689 }
690 return pos;
691 }
692
693 bool Document::IsWordAt(int start, int end) {
694 int lengthDoc = Length();
695 if (start > 0) {
696 char ch = CharAt(start - 1);
697 if (IsWordChar(ch))
698 return false;
699 }
700 if (end < lengthDoc - 1) {
701 char ch = CharAt(end);
702 if (IsWordChar(ch))
703 return false;
704 }
705 return true;
706 }
707
708 // Find text in document, supporting both forward and backward
709 // searches (just pass minPos > maxPos to do a backward search)
710 // Has not been tested with backwards DBCS searches yet.
711 long Document::FindText(int minPos, int maxPos, const char *s, bool caseSensitive, bool word) {
712 bool forward = minPos <= maxPos;
713 int increment = forward ? 1 : -1;
714
715 // Range endpoints should not be inside DBCS characters, but just in case, move them.
716 int startPos = MovePositionOutsideChar(minPos, increment, false);
717 int endPos = MovePositionOutsideChar(maxPos, increment, false);
718
719 // Compute actual search ranges needed
720 int lengthFind = strlen(s);
721 int endSearch = endPos;
722 if (startPos <= endPos) {
723 endSearch = endPos - lengthFind + 1;
724 }
725 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
726 char firstChar = s[0];
727 if (!caseSensitive)
728 firstChar = static_cast<char>(toupper(firstChar));
729 int pos = startPos;
730 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
731 char ch = CharAt(pos);
732 if (caseSensitive) {
733 if (ch == firstChar) {
734 bool found = true;
735 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
736 ch = CharAt(pos + posMatch);
737 if (ch != s[posMatch])
738 found = false;
739 }
740 if (found) {
741 if ((!word) || IsWordAt(pos, pos + lengthFind))
742 return pos;
743 }
744 }
745 } else {
746 if (toupper(ch) == firstChar) {
747 bool found = true;
748 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
749 ch = CharAt(pos + posMatch);
750 if (toupper(ch) != toupper(s[posMatch]))
751 found = false;
752 }
753 if (found) {
754 if ((!word) || IsWordAt(pos, pos + lengthFind))
755 return pos;
756 }
757 }
758 }
759 pos += increment;
760 if (dbcsCodePage) {
761 // Ensure trying to match from start of character
762 pos = MovePositionOutsideChar(pos, increment, false);
763 }
764 }
765 //Platform::DebugPrintf("Not found\n");
766 return - 1;
767 }
768
769 int Document::LinesTotal() {
770 return cb.Lines();
771 }
772
773 void Document::ChangeCase(Range r, bool makeUpperCase) {
774 for (int pos=r.start; pos<r.end; pos++) {
775 char ch = CharAt(pos);
776 if (dbcsCodePage && IsDBCS(pos)) {
777 pos += LenChar(pos);
778 } else {
779 if (makeUpperCase) {
780 if (islower(ch)) {
781 ChangeChar(pos, static_cast<char>(toupper(ch)));
782 }
783 } else {
784 if (isupper(ch)) {
785 ChangeChar(pos, static_cast<char>(tolower(ch)));
786 }
787 }
788 }
789 }
790 }
791
792 void Document::SetWordChars(unsigned char *chars) {
793 int ch;
794 for (ch = 0; ch < 256; ch++) {
795 wordchars[ch] = false;
796 }
797 if (chars) {
798 while (*chars) {
799 wordchars[*chars] = true;
800 chars++;
801 }
802 } else {
803 for (ch = 0; ch < 256; ch++) {
804 wordchars[ch] = isalnum(ch) || ch == '_';
805 }
806 }
807 }
808
809 void Document::SetStylingBits(int bits) {
810 stylingBits = bits;
811 stylingBitsMask = 0;
812 for (int bit=0; bit<stylingBits; bit++) {
813 stylingBitsMask <<= 1;
814 stylingBitsMask |= 1;
815 }
816 }
817
818 void Document::StartStyling(int position, char mask) {
819 stylingPos = position;
820 stylingMask = mask;
821 }
822
823 void Document::SetStyleFor(int length, char style) {
824 if (enteredCount == 0) {
825 enteredCount++;
826 int prevEndStyled = endStyled;
827 if (cb.SetStyleFor(stylingPos, length, style, stylingMask)) {
828 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
829 prevEndStyled, length);
830 NotifyModified(mh);
831 }
832 stylingPos += length;
833 endStyled = stylingPos;
834 enteredCount--;
835 }
836 }
837
838 void Document::SetStyles(int length, char *styles) {
839 if (enteredCount == 0) {
840 enteredCount++;
841 int prevEndStyled = endStyled;
842 bool didChange = false;
843 for (int iPos = 0; iPos < length; iPos++, stylingPos++) {
844 if (cb.SetStyleAt(stylingPos, styles[iPos], stylingMask)) {
845 didChange = true;
846 }
847 }
848 endStyled = stylingPos;
849 if (didChange) {
850 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
851 prevEndStyled, endStyled - prevEndStyled);
852 NotifyModified(mh);
853 }
854 enteredCount--;
855 }
856 }
857
858 bool Document::EnsureStyledTo(int pos) {
859 // Ask the watchers to style, and stop as soon as one responds.
860 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++)
861 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
862 return pos <= GetEndStyled();
863 }
864
865 bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
866 for (int i = 0; i < lenWatchers; i++) {
867 if ((watchers[i].watcher == watcher) &&
868 (watchers[i].userData == userData))
869 return false;
870 }
871 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
872 if (!pwNew)
873 return false;
874 for (int j = 0; j < lenWatchers; j++)
875 pwNew[j] = watchers[j];
876 pwNew[lenWatchers].watcher = watcher;
877 pwNew[lenWatchers].userData = userData;
878 delete []watchers;
879 watchers = pwNew;
880 lenWatchers++;
881 return true;
882 }
883
884 bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
885 for (int i = 0; i < lenWatchers; i++) {
886 if ((watchers[i].watcher == watcher) &&
887 (watchers[i].userData == userData)) {
888 if (lenWatchers == 1) {
889 delete []watchers;
890 watchers = 0;
891 lenWatchers = 0;
892 } else {
893 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
894 if (!pwNew)
895 return false;
896 for (int j = 0; j < lenWatchers - 1; j++) {
897 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
898 }
899 delete []watchers;
900 watchers = pwNew;
901 lenWatchers--;
902 }
903 return true;
904 }
905 }
906 return false;
907 }
908
909 void Document::NotifyModifyAttempt() {
910 for (int i = 0; i < lenWatchers; i++) {
911 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
912 }
913 }
914
915 void Document::NotifySavePoint(bool atSavePoint) {
916 for (int i = 0; i < lenWatchers; i++) {
917 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
918 }
919 }
920
921 void Document::NotifyModified(DocModification mh) {
922 for (int i = 0; i < lenWatchers; i++) {
923 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
924 }
925 }