]>
git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
1 // Scintilla source code edit control
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
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.
15 #include "Scintilla.h"
17 #include "CellBuffer.h"
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));
26 Document::Document() {
31 eolMode
= SC_EOL_CRLF
;
35 stylingBitsMask
= 0x1F;
37 for (int ch
= 0; ch
< 256; ch
++) {
38 wordchars
[ch
] = isalnum(ch
) || ch
== '_';
42 enteredReadOnlyCount
= 0;
47 backspaceUnindents
= false;
56 Document::~Document() {
57 for (int i
= 0; i
< lenWatchers
; i
++) {
58 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
69 // Increase reference count and return its previous value.
70 int Document::AddRef() {
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
;
83 void Document::SetSavePoint() {
85 NotifySavePoint(true);
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);
95 void Document::DeleteMark(int line
, int markerNum
) {
96 cb
.DeleteMark(line
, markerNum
);
97 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
101 void Document::DeleteMarkFromHandle(int markerHandle
) {
102 cb
.DeleteMarkFromHandle(markerHandle
);
103 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
107 void Document::DeleteAllMarks(int markerNum
) {
108 cb
.DeleteAllMarks(markerNum
);
109 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
113 int Document::LineStart(int line
) {
114 return cb
.LineStart(line
);
117 int Document::LineEnd(int line
) {
118 if (line
== LinesTotal() - 1) {
119 return LineStart(line
+ 1);
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')) {
130 int Document::LineFromPosition(int pos
) {
131 return cb
.LineFromPosition(pos
);
134 int Document::LineEndPosition(int position
) {
135 return LineEnd(LineFromPosition(position
));
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' ) )
145 if (position
== startText
)
146 return startPosition
;
151 int Document::SetLevel(int line
, int level
) {
152 int prev
= cb
.SetLevel(line
, level
);
154 DocModification
mh(SC_MOD_CHANGEFOLD
| SC_MOD_CHANGEMARKER
,
155 LineStart(line
), 0, 0, 0);
157 mh
.foldLevelNow
= level
;
158 mh
.foldLevelPrev
= prev
;
164 static bool IsSubordinate(int levelStart
, int levelTry
) {
165 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
168 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
171 int Document::GetLastChild(int lineParent
, int level
) {
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)))
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
) {
190 return lineMaxSubord
;
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
))
202 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
203 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
210 int Document::ClampPositionIntoDocument(int pos
) {
211 return Platform::Clamp(pos
, 0, Length());
214 bool Document::IsCrLf(int pos
) {
217 if (pos
>= (Length() - 1))
219 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
223 bool Document::IsDBCS(int pos
) {
225 if (SC_CP_UTF8
== dbcsCodePage
) {
226 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
229 // Anchor DBCS calculations at start of line because start of line can
230 // not be a DBCS trail byte.
232 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
234 while (startLine
<= pos
) {
235 if (IsDBCSLeadByteEx(dbcsCodePage
, cb
.CharAt(startLine
))) {
237 if (startLine
>= pos
)
247 // PLAT_GTK or PLAT_WX
248 // TODO: support DBCS under GTK+ and WX
249 bool Document::IsDBCS(int) {
254 int Document::LenChar(int pos
) {
257 } else if (SC_CP_UTF8
== dbcsCodePage
) {
258 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
262 if (ch
>= (0x80 + 0x40 + 0x20))
264 int lengthDoc
= Length();
265 if ((pos
+ len
) > lengthDoc
)
266 return lengthDoc
-pos
;
269 } else if (IsDBCS(pos
)) {
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
289 // Position 0 and Length() can not be between any two characters
295 // assert pos > 0 && pos < Length()
296 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
303 // Not between CR and LF
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
315 ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
318 // Anchor DBCS calculations at start of line because start of line can
319 // not be a DBCS trail byte.
321 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
323 bool atLeadByte
= false;
324 while (startLine
< pos
) {
327 else if (IsDBCSLeadByteEx(dbcsCodePage
, cb
.CharAt(startLine
)))
332 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
337 // Position is between a lead byte and a trail byte
350 void Document::ModifiedAt(int pos
) {
355 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
356 // SetStyleAt does not change the persistent state of a document
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
) {
362 if ((pos
+ len
) > Length())
364 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
365 enteredReadOnlyCount
++;
366 NotifyModifyAttempt();
367 enteredReadOnlyCount
--;
369 if (enteredCount
== 0) {
371 if (!cb
.IsReadOnly()) {
374 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
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))
388 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
,
390 LinesTotal() - prevLinesTotal
, text
));
396 void Document::InsertStyledString(int position
, char *s
, int insertLength
) {
397 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
398 enteredReadOnlyCount
++;
399 NotifyModifyAttempt();
400 enteredReadOnlyCount
--;
402 if (enteredCount
== 0) {
404 if (!cb
.IsReadOnly()) {
407 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
408 position
/ 2, insertLength
/ 2,
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);
418 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
,
419 position
/ 2, insertLength
/ 2,
420 LinesTotal() - prevLinesTotal
, text
));
426 int Document::Undo() {
428 if (enteredCount
== 0) {
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
));
440 NotifyModified(DocModification(
441 SC_MOD_BEFOREDELETE
| SC_PERFORMED_UNDO
, action
));
443 cb
.PerformUndoStep();
444 int cellPosition
= action
.position
/ 2;
445 ModifiedAt(cellPosition
);
446 newPos
= cellPosition
;
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
;
454 modFlags
|= SC_MOD_DELETETEXT
;
456 if (step
== steps
- 1)
457 modFlags
|= SC_LASTSTEPINUNDOREDO
;
458 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
459 LinesTotal() - prevLinesTotal
, action
.data
));
462 bool endSavePoint
= cb
.IsSavePoint();
463 if (startSavePoint
!= endSavePoint
)
464 NotifySavePoint(endSavePoint
);
470 int Document::Redo() {
472 if (enteredCount
== 0) {
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
));
483 NotifyModified(DocModification(
484 SC_MOD_BEFOREDELETE
| SC_PERFORMED_REDO
, action
));
486 cb
.PerformRedoStep();
487 ModifiedAt(action
.position
/ 2);
488 newPos
= action
.position
/ 2;
490 int modFlags
= SC_PERFORMED_REDO
;
491 if (action
.at
== insertAction
) {
492 newPos
+= action
.lenData
;
493 modFlags
|= SC_MOD_INSERTTEXT
;
495 modFlags
|= SC_MOD_DELETETEXT
;
497 if (step
== steps
- 1)
498 modFlags
|= SC_LASTSTEPINUNDOREDO
;
500 DocModification(modFlags
, action
.position
/ 2, action
.lenData
,
501 LinesTotal() - prevLinesTotal
, action
.data
));
504 bool endSavePoint
= cb
.IsSavePoint();
505 if (startSavePoint
!= endSavePoint
)
506 NotifySavePoint(endSavePoint
);
512 void Document::InsertChar(int pos
, char ch
) {
516 InsertStyledString(pos
*2, chs
, 2);
519 // Insert a null terminated string
520 void Document::InsertString(int position
, const char *s
) {
521 InsertString(position
, s
, strlen(s
));
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];
528 for (int i
= 0; i
< insertLength
; i
++) {
529 sWithStyle
[i
*2] = s
[i
];
530 sWithStyle
[i
*2 + 1] = 0;
532 InsertStyledString(position
*2, sWithStyle
, insertLength
*2);
537 void Document::ChangeChar(int pos
, char ch
) {
542 void Document::DelChar(int pos
) {
543 DeleteChars(pos
, LenChar(pos
));
546 int Document::DelCharBack(int pos
) {
549 } else if (IsCrLf(pos
- 2)) {
550 DeleteChars(pos
- 2, 2);
552 } else if (SC_CP_UTF8
== dbcsCodePage
) {
553 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
554 DeleteChars(startChar
, pos
- startChar
);
556 } else if (IsDBCS(pos
- 1)) {
557 DeleteChars(pos
- 2, 2);
560 DeleteChars(pos
- 1, 1);
565 static bool isindentchar(char ch
) {
566 return (ch
== ' ') || (ch
== '\t');
569 static int NextTab(int pos
, int tabSize
) {
570 return ((pos
/ tabSize
) + 1) * tabSize
;
573 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
574 length
--; // ensure space for \0
576 while ((indent
>= tabSize
) && (length
> 0)) {
582 while ((indent
> 0) && (length
> 0)) {
590 int Document::GetLineIndentation(int line
) {
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
);
600 indent
= NextTab(indent
, tabInChars
);
608 void Document::SetLineIndentation(int line
, int indent
) {
609 int indentOfLine
= GetLineIndentation(line
);
612 if (indent
!= indentOfLine
) {
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
);
622 int Document::GetLineIndentPosition(int line
) {
625 int pos
= LineStart(line
);
626 int length
= Length();
627 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
633 int Document::GetColumn(int pos
) {
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
);
640 column
= NextTab(column
, tabInChars
);
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
);
657 SetLineIndentation(line
, indentOfLine
+ IndentSize());
659 SetLineIndentation(line
, indentOfLine
- IndentSize());
663 void Document::ConvertLineEnds(int eolModeSet
) {
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
) {
670 if (eolModeSet
== SC_EOL_CR
)
671 InsertString(pos
, "\r", 1);
673 InsertString(pos
, "\n", 1);
678 if (eolModeSet
!= SC_EOL_CR
) {
680 if (eolModeSet
== SC_EOL_CRLF
) {
681 InsertString(pos
, "\r\n", 2);
684 InsertString(pos
, "\n", 1);
688 } else if (cb
.CharAt(pos
) == '\n') {
689 if (eolModeSet
!= SC_EOL_LF
) {
691 if (eolModeSet
== SC_EOL_CRLF
) {
692 InsertString(pos
, "\r\n", 2);
695 InsertString(pos
, "\r", 1);
703 bool Document::IsWordChar(unsigned char ch
) {
704 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
> 0x80))
706 return wordchars
[ch
];
709 int Document::ExtendWordSelect(int pos
, int delta
) {
711 while (pos
> 0 && IsWordChar(cb
.CharAt(pos
- 1)))
714 while (pos
< (Length()) && IsWordChar(cb
.CharAt(pos
)))
720 int Document::NextWordStart(int pos
, int delta
) {
722 while (pos
> 0 && (cb
.CharAt(pos
- 1) == ' ' || cb
.CharAt(pos
- 1) == '\t'))
724 if (isspacechar(cb
.CharAt(pos
- 1))) { // Back up to previous line
725 while (pos
> 0 && isspacechar(cb
.CharAt(pos
- 1)))
728 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
- 1));
729 while (pos
> 0 && !isspacechar(cb
.CharAt(pos
- 1)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
- 1))))
733 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
));
734 while (pos
< (Length()) && isspacechar(cb
.CharAt(pos
)))
736 while (pos
< (Length()) && !isspacechar(cb
.CharAt(pos
)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
))))
738 while (pos
< (Length()) && (cb
.CharAt(pos
) == ' ' || cb
.CharAt(pos
) == '\t'))
745 * Check that the character before the given position
746 * is not a word character.
748 bool Document::IsWordStartAt(int pos
) {
750 return !IsWordChar(CharAt(pos
- 1));
756 * Check that the character after the given position
757 * is not a word character.
759 bool Document::IsWordEndAt(int pos
) {
760 if (pos
< Length() - 1) {
761 return !IsWordChar(CharAt(pos
));
767 * Check that the given range is delimited by
768 * non word characters.
770 bool Document::IsWordAt(int start
, int end
) {
771 return IsWordStartAt(start
) && IsWordEndAt(end
);
774 // The comparison and case changing functions here assume ASCII
775 // or extended ASCII such as the normal Windows code page.
777 static inline char MakeUpperCase(char ch
) {
778 if (ch
< 'a' || ch
> 'z')
781 return static_cast<char>(ch
- 'a' + 'A');
784 static inline char MakeLowerCase(char ch
) {
785 if (ch
< 'A' || ch
> 'Z')
788 return static_cast<char>(ch
- 'A' + 'a');
791 // Define a way for the Regular Expression code to access the document
792 class DocumentIndexer
: public CharacterIndexer
{
796 DocumentIndexer(Document
*pdoc_
, int end_
) :
797 pdoc(pdoc_
), end(end_
) {}
799 virtual char CharAt(int index
) {
800 if (index
< 0 || index
>= end
)
803 return pdoc
->CharAt(index
);
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.
812 long Document::FindText(int minPos
, int maxPos
, const char *s
,
813 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
,
817 pre
= new RESearch();
824 if (minPos
<= maxPos
) {
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);
836 const char *errmsg
= pre
->Compile(s
, *length
, caseSensitive
);
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_.]+\))
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.
849 startPos
= LineStart(lineRangeStart
);
853 char searchEnd
= s
[*length
- 1];
855 // These produce empty selections so nudge them on if needed
857 if (startPos
== LineStart(lineRangeStart
))
859 } else if (s
[0] == '$') {
860 if ((startPos
== LineEnd(lineRangeStart
)) && (lineRangeStart
< lineRangeEnd
))
861 startPos
= LineStart(lineRangeStart
+ 1);
863 lineRangeStart
= LineFromPosition(startPos
);
864 lineRangeEnd
= LineFromPosition(endPos
);
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
;
874 if (line
== lineRangeEnd
) {
875 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
876 continue; // Can't match end of line if end position before end of line
879 DocumentIndexer
di(this, endOfLine
);
880 int success
= pre
->Execute(di
, startOfLine
, endOfLine
);
883 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
892 bool forward
= minPos
<= maxPos
;
893 int increment
= forward
? 1 : -1;
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);
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;
907 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
908 char firstChar
= s
[0];
910 firstChar
= static_cast<char>(MakeUpperCase(firstChar
));
912 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
913 char ch
= CharAt(pos
);
915 if (ch
== firstChar
) {
917 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
918 ch
= CharAt(pos
+ posMatch
);
919 if (ch
!= s
[posMatch
])
923 if ((!word
&& !wordStart
) ||
924 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
925 wordStart
&& IsWordStartAt(pos
))
930 if (MakeUpperCase(ch
) == firstChar
) {
932 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
933 ch
= CharAt(pos
+ posMatch
);
934 if (MakeUpperCase(ch
) != MakeUpperCase(s
[posMatch
]))
938 if ((!word
&& !wordStart
) ||
939 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
940 wordStart
&& IsWordStartAt(pos
))
947 // Ensure trying to match from start of character
948 pos
= MovePositionOutsideChar(pos
, increment
, false);
952 //Platform::DebugPrintf("Not found\n");
956 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
959 delete []substituted
;
961 DocumentIndexer
di(this, Length());
962 if (!pre
->GrabMatches(di
))
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
];
974 substituted
= new char[lenResult
+ 1];
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
);
995 int Document::LinesTotal() {
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
);
1005 if (makeUpperCase
) {
1007 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1011 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1018 void Document::SetWordChars(unsigned char *chars
) {
1020 for (ch
= 0; ch
< 256; ch
++) {
1021 wordchars
[ch
] = false;
1025 wordchars
[*chars
] = true;
1029 for (ch
= 0; ch
< 256; ch
++) {
1030 wordchars
[ch
] = isalnum(ch
) || ch
== '_';
1035 void Document::SetStylingBits(int bits
) {
1037 stylingBitsMask
= 0;
1038 for (int bit
= 0; bit
< stylingBits
; bit
++) {
1039 stylingBitsMask
<<= 1;
1040 stylingBitsMask
|= 1;
1044 void Document::StartStyling(int position
, char mask
) {
1046 endStyled
= position
;
1049 void Document::SetStyleFor(int length
, char style
) {
1050 if (enteredCount
== 0) {
1052 int prevEndStyled
= endStyled
;
1053 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1054 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1055 prevEndStyled
, length
);
1058 endStyled
+= length
;
1063 void Document::SetStyles(int length
, char *styles
) {
1064 if (enteredCount
== 0) {
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
)) {
1074 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1075 prevEndStyled
, endStyled
- prevEndStyled
);
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();
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
))
1095 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1098 for (int j
= 0; j
< lenWatchers
; j
++)
1099 pwNew
[j
] = watchers
[j
];
1100 pwNew
[lenWatchers
].watcher
= watcher
;
1101 pwNew
[lenWatchers
].userData
= userData
;
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) {
1117 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1120 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1121 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1133 void Document::NotifyModifyAttempt() {
1134 for (int i
= 0; i
< lenWatchers
; i
++) {
1135 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1139 void Document::NotifySavePoint(bool atSavePoint
) {
1140 for (int i
= 0; i
< lenWatchers
; i
++) {
1141 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1145 void Document::NotifyModified(DocModification mh
) {
1146 for (int i
= 0; i
< lenWatchers
; i
++) {
1147 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1151 bool Document::IsWordPartSeparator(char ch
) {
1152 return ispunct(ch
) && IsWordChar(ch
);
1155 int Document::WordPartLeft(int pos
) {
1158 char startChar
= cb
.CharAt(pos
);
1159 if (IsWordPartSeparator(startChar
)) {
1160 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1165 startChar
= cb
.CharAt(pos
);
1167 if (islower(startChar
)) {
1168 while (pos
> 0 && islower(cb
.CharAt(pos
)))
1170 if (!isupper(cb
.CharAt(pos
)) && !islower(cb
.CharAt(pos
)))
1172 } else if (isupper(startChar
)) {
1173 while (pos
> 0 && isupper(cb
.CharAt(pos
)))
1175 if (!isupper(cb
.CharAt(pos
)))
1177 } else if (isdigit(startChar
)) {
1178 while (pos
> 0 && isdigit(cb
.CharAt(pos
)))
1180 if (!isdigit(cb
.CharAt(pos
)))
1182 } else if (ispunct(startChar
)) {
1183 while (pos
> 0 && ispunct(cb
.CharAt(pos
)))
1185 if (!ispunct(cb
.CharAt(pos
)))
1187 } else if (isspacechar(startChar
)) {
1188 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1190 if (!isspacechar(cb
.CharAt(pos
)))
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
)))
1204 startChar
= cb
.CharAt(pos
);
1206 if (islower(startChar
)) {
1207 while (pos
< length
&& islower(cb
.CharAt(pos
)))
1209 } else if (isupper(startChar
)) {
1210 if (islower(cb
.CharAt(pos
+ 1))) {
1212 while (pos
< length
&& islower(cb
.CharAt(pos
)))
1215 while (pos
< length
&& isupper(cb
.CharAt(pos
)))
1218 if (islower(cb
.CharAt(pos
)) && isupper(cb
.CharAt(pos
- 1)))
1220 } else if (isdigit(startChar
)) {
1221 while (pos
< length
&& isdigit(cb
.CharAt(pos
)))
1223 } else if (ispunct(startChar
)) {
1224 while (pos
< length
&& ispunct(cb
.CharAt(pos
)))
1226 } else if (isspacechar(startChar
)) {
1227 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))