]>
git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
10403242b9378ffc64a181c3de46197a753fddbd
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
) {
360 if ((pos
+ len
) > Length())
362 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
363 enteredReadOnlyCount
++;
364 NotifyModifyAttempt();
365 enteredReadOnlyCount
--;
367 if (enteredCount
== 0) {
369 if (!cb
.IsReadOnly()) {
372 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
375 int prevLinesTotal
= LinesTotal();
376 bool startSavePoint
= cb
.IsSavePoint();
377 const char *text
= cb
.DeleteChars(pos
* 2, len
* 2);
378 if (startSavePoint
&& cb
.IsCollectingUndo())
379 NotifySavePoint(!startSavePoint
);
380 if ((pos
< Length()) || (pos
== 0))
386 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
,
388 LinesTotal() - prevLinesTotal
, text
));
394 void Document::InsertStyledString(int position
, char *s
, int insertLength
) {
395 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
396 enteredReadOnlyCount
++;
397 NotifyModifyAttempt();
398 enteredReadOnlyCount
--;
400 if (enteredCount
== 0) {
402 if (!cb
.IsReadOnly()) {
405 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
406 position
/ 2, insertLength
/ 2,
408 int prevLinesTotal
= LinesTotal();
409 bool startSavePoint
= cb
.IsSavePoint();
410 const char *text
= cb
.InsertString(position
, s
, insertLength
);
411 if (startSavePoint
&& cb
.IsCollectingUndo())
412 NotifySavePoint(!startSavePoint
);
413 ModifiedAt(position
/ 2);
416 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
,
417 position
/ 2, insertLength
/ 2,
418 LinesTotal() - prevLinesTotal
, text
));
424 int Document::Undo() {
426 if (enteredCount
== 0) {
428 bool startSavePoint
= cb
.IsSavePoint();
429 int steps
= cb
.StartUndo();
430 //Platform::DebugPrintf("Steps=%d\n", steps);
431 for (int step
= 0; step
< steps
; step
++) {
432 int prevLinesTotal
= LinesTotal();
433 const Action
&action
= cb
.GetUndoStep();
434 if (action
.at
== removeAction
) {
435 NotifyModified(DocModification(
436 SC_MOD_BEFOREINSERT
| SC_PERFORMED_UNDO
, action
));
438 NotifyModified(DocModification(
439 SC_MOD_BEFOREDELETE
| SC_PERFORMED_UNDO
, action
));
441 cb
.PerformUndoStep();
442 int cellPosition
= action
.position
/ 2;
443 ModifiedAt(cellPosition
);
444 newPos
= cellPosition
;
446 int modFlags
= SC_PERFORMED_UNDO
;
447 // With undo, an insertion action becomes a deletion notification
448 if (action
.at
== removeAction
) {
449 newPos
+= action
.lenData
;
450 modFlags
|= SC_MOD_INSERTTEXT
;
452 modFlags
|= SC_MOD_DELETETEXT
;
454 if (step
== steps
- 1)
455 modFlags
|= SC_LASTSTEPINUNDOREDO
;
456 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
457 LinesTotal() - prevLinesTotal
, action
.data
));
460 bool endSavePoint
= cb
.IsSavePoint();
461 if (startSavePoint
!= endSavePoint
)
462 NotifySavePoint(endSavePoint
);
468 int Document::Redo() {
470 if (enteredCount
== 0) {
472 bool startSavePoint
= cb
.IsSavePoint();
473 int steps
= cb
.StartRedo();
474 for (int step
= 0; step
< steps
; step
++) {
475 int prevLinesTotal
= LinesTotal();
476 const Action
&action
= cb
.GetRedoStep();
477 if (action
.at
== insertAction
) {
478 NotifyModified(DocModification(
479 SC_MOD_BEFOREINSERT
| SC_PERFORMED_REDO
, action
));
481 NotifyModified(DocModification(
482 SC_MOD_BEFOREDELETE
| SC_PERFORMED_REDO
, action
));
484 cb
.PerformRedoStep();
485 ModifiedAt(action
.position
/ 2);
486 newPos
= action
.position
/ 2;
488 int modFlags
= SC_PERFORMED_REDO
;
489 if (action
.at
== insertAction
) {
490 newPos
+= action
.lenData
;
491 modFlags
|= SC_MOD_INSERTTEXT
;
493 modFlags
|= SC_MOD_DELETETEXT
;
495 if (step
== steps
- 1)
496 modFlags
|= SC_LASTSTEPINUNDOREDO
;
498 DocModification(modFlags
, action
.position
/ 2, action
.lenData
,
499 LinesTotal() - prevLinesTotal
, action
.data
));
502 bool endSavePoint
= cb
.IsSavePoint();
503 if (startSavePoint
!= endSavePoint
)
504 NotifySavePoint(endSavePoint
);
510 void Document::InsertChar(int pos
, char ch
) {
514 InsertStyledString(pos
*2, chs
, 2);
517 // Insert a null terminated string
518 void Document::InsertString(int position
, const char *s
) {
519 InsertString(position
, s
, strlen(s
));
522 // Insert a string with a length
523 void Document::InsertString(int position
, const char *s
, int insertLength
) {
524 char *sWithStyle
= new char[insertLength
* 2];
526 for (int i
= 0; i
< insertLength
; i
++) {
527 sWithStyle
[i
*2] = s
[i
];
528 sWithStyle
[i
*2 + 1] = 0;
530 InsertStyledString(position
*2, sWithStyle
, insertLength
*2);
535 void Document::ChangeChar(int pos
, char ch
) {
540 void Document::DelChar(int pos
) {
541 DeleteChars(pos
, LenChar(pos
));
544 int Document::DelCharBack(int pos
) {
547 } else if (IsCrLf(pos
- 2)) {
548 DeleteChars(pos
- 2, 2);
550 } else if (SC_CP_UTF8
== dbcsCodePage
) {
551 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
552 DeleteChars(startChar
, pos
- startChar
);
554 } else if (IsDBCS(pos
- 1)) {
555 DeleteChars(pos
- 2, 2);
558 DeleteChars(pos
- 1, 1);
563 static bool isindentchar(char ch
) {
564 return (ch
== ' ') || (ch
== '\t');
567 static int NextTab(int pos
, int tabSize
) {
568 return ((pos
/ tabSize
) + 1) * tabSize
;
571 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
572 length
--; // ensure space for \0
574 while ((indent
>= tabSize
) && (length
> 0)) {
580 while ((indent
> 0) && (length
> 0)) {
588 int Document::GetLineIndentation(int line
) {
590 if ((line
>= 0) && (line
< LinesTotal())) {
591 int lineStart
= LineStart(line
);
592 int length
= Length();
593 for (int i
= lineStart
;i
< length
;i
++) {
594 char ch
= cb
.CharAt(i
);
598 indent
= NextTab(indent
, tabInChars
);
606 void Document::SetLineIndentation(int line
, int indent
) {
607 int indentOfLine
= GetLineIndentation(line
);
610 if (indent
!= indentOfLine
) {
612 CreateIndentation(linebuf
, sizeof(linebuf
), indent
, tabInChars
, !useTabs
);
613 int thisLineStart
= LineStart(line
);
614 int indentPos
= GetLineIndentPosition(line
);
615 DeleteChars(thisLineStart
, indentPos
- thisLineStart
);
616 InsertString(thisLineStart
, linebuf
);
620 int Document::GetLineIndentPosition(int line
) {
623 int pos
= LineStart(line
);
624 int length
= Length();
625 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
631 int Document::GetColumn(int pos
) {
633 int line
= LineFromPosition(pos
);
634 if ((line
>= 0) && (line
< LinesTotal())) {
635 for (int i
= LineStart(line
);i
< pos
;i
++) {
636 char ch
= cb
.CharAt(i
);
638 column
= NextTab(column
, tabInChars
);
650 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
651 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
652 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
653 int indentOfLine
= GetLineIndentation(line
);
655 SetLineIndentation(line
, indentOfLine
+ IndentSize());
657 SetLineIndentation(line
, indentOfLine
- IndentSize());
661 void Document::ConvertLineEnds(int eolModeSet
) {
663 for (int pos
= 0; pos
< Length(); pos
++) {
664 if (cb
.CharAt(pos
) == '\r') {
665 if (cb
.CharAt(pos
+ 1) == '\n') {
666 if (eolModeSet
!= SC_EOL_CRLF
) {
668 if (eolModeSet
== SC_EOL_CR
)
669 InsertString(pos
, "\r", 1);
671 InsertString(pos
, "\n", 1);
676 if (eolModeSet
!= SC_EOL_CR
) {
678 if (eolModeSet
== SC_EOL_CRLF
) {
679 InsertString(pos
, "\r\n", 2);
682 InsertString(pos
, "\n", 1);
686 } else if (cb
.CharAt(pos
) == '\n') {
687 if (eolModeSet
!= SC_EOL_LF
) {
689 if (eolModeSet
== SC_EOL_CRLF
) {
690 InsertString(pos
, "\r\n", 2);
693 InsertString(pos
, "\r", 1);
701 bool Document::IsWordChar(unsigned char ch
) {
702 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
> 0x80))
704 return wordchars
[ch
];
707 int Document::ExtendWordSelect(int pos
, int delta
) {
709 while (pos
> 0 && IsWordChar(cb
.CharAt(pos
- 1)))
712 while (pos
< (Length()) && IsWordChar(cb
.CharAt(pos
)))
718 int Document::NextWordStart(int pos
, int delta
) {
720 while (pos
> 0 && (cb
.CharAt(pos
- 1) == ' ' || cb
.CharAt(pos
- 1) == '\t'))
722 if (isspacechar(cb
.CharAt(pos
- 1))) { // Back up to previous line
723 while (pos
> 0 && isspacechar(cb
.CharAt(pos
- 1)))
726 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
- 1));
727 while (pos
> 0 && !isspacechar(cb
.CharAt(pos
- 1)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
- 1))))
731 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
));
732 while (pos
< (Length()) && isspacechar(cb
.CharAt(pos
)))
734 while (pos
< (Length()) && !isspacechar(cb
.CharAt(pos
)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
))))
736 while (pos
< (Length()) && (cb
.CharAt(pos
) == ' ' || cb
.CharAt(pos
) == '\t'))
743 * Check that the character before the given position
744 * is not a word character.
746 bool Document::IsWordStartAt(int pos
) {
748 return !IsWordChar(CharAt(pos
- 1));
754 * Check that the character after the given position
755 * is not a word character.
757 bool Document::IsWordEndAt(int pos
) {
758 if (pos
< Length() - 1) {
759 return !IsWordChar(CharAt(pos
));
765 * Check that the given range is delimited by
766 * non word characters.
768 bool Document::IsWordAt(int start
, int end
) {
769 return IsWordStartAt(start
) && IsWordEndAt(end
);
772 // The comparison and case changing functions here assume ASCII
773 // or extended ASCII such as the normal Windows code page.
775 static inline char MakeUpperCase(char ch
) {
776 if (ch
< 'a' || ch
> 'z')
779 return static_cast<char>(ch
- 'a' + 'A');
782 static inline char MakeLowerCase(char ch
) {
783 if (ch
< 'A' || ch
> 'Z')
786 return static_cast<char>(ch
- 'A' + 'a');
789 // Define a way for the Regular Expression code to access the document
790 class DocumentIndexer
: public CharacterIndexer
{
794 DocumentIndexer(Document
*pdoc_
, int end_
) :
795 pdoc(pdoc_
), end(end_
) {}
797 virtual char CharAt(int index
) {
798 if (index
< 0 || index
>= end
)
801 return pdoc
->CharAt(index
);
806 * Find text in document, supporting both forward and backward
807 * searches (just pass minPos > maxPos to do a backward search)
808 * Has not been tested with backwards DBCS searches yet.
810 long Document::FindText(int minPos
, int maxPos
, const char *s
,
811 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
,
815 pre
= new RESearch();
822 if (minPos
<= maxPos
) {
830 // Range endpoints should not be inside DBCS characters, but just in case, move them.
831 startPos
= MovePositionOutsideChar(startPos
, 1, false);
832 endPos
= MovePositionOutsideChar(endPos
, 1, false);
834 const char *errmsg
= pre
->Compile(s
, *length
, caseSensitive
);
838 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
839 // Replace first '.' with '-' in each property file variable reference:
840 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
842 int lineRangeStart
= LineFromPosition(startPos
);
843 int lineRangeEnd
= LineFromPosition(endPos
);
844 if ((startPos
>= LineEnd(lineRangeStart
)) && (lineRangeStart
< lineRangeEnd
)) {
845 // the start position is at end of line or between line end characters.
847 startPos
= LineStart(lineRangeStart
);
851 char searchEnd
= s
[*length
- 1];
853 // These produce empty selections so nudge them on if needed
855 if (startPos
== LineStart(lineRangeStart
))
857 } else if (s
[0] == '$') {
858 if ((startPos
== LineEnd(lineRangeStart
)) && (lineRangeStart
< lineRangeEnd
))
859 startPos
= LineStart(lineRangeStart
+ 1);
861 lineRangeStart
= LineFromPosition(startPos
);
862 lineRangeEnd
= LineFromPosition(endPos
);
864 for (int line
= lineRangeStart
; line
<= lineRangeEnd
; line
++) {
865 int startOfLine
= LineStart(line
);
866 int endOfLine
= LineEnd(line
);
867 if (line
== lineRangeStart
) {
868 if ((startPos
!= startOfLine
) && (s
[0] == '^'))
869 continue; // Can't match start of line if start position after start of line
870 startOfLine
= startPos
;
872 if (line
== lineRangeEnd
) {
873 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
874 continue; // Can't match end of line if end position before end of line
877 DocumentIndexer
di(this, endOfLine
);
878 int success
= pre
->Execute(di
, startOfLine
, endOfLine
);
881 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
890 bool forward
= minPos
<= maxPos
;
891 int increment
= forward
? 1 : -1;
893 // Range endpoints should not be inside DBCS characters, but just in case, move them.
894 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
895 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
897 // Compute actual search ranges needed
898 int lengthFind
= *length
;
899 if (lengthFind
== -1)
900 lengthFind
= strlen(s
);
901 int endSearch
= endPos
;
902 if (startPos
<= endPos
) {
903 endSearch
= endPos
- lengthFind
+ 1;
905 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
906 char firstChar
= s
[0];
908 firstChar
= static_cast<char>(MakeUpperCase(firstChar
));
910 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
911 char ch
= CharAt(pos
);
913 if (ch
== firstChar
) {
915 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
916 ch
= CharAt(pos
+ posMatch
);
917 if (ch
!= s
[posMatch
])
921 if ((!word
&& !wordStart
) ||
922 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
923 wordStart
&& IsWordStartAt(pos
))
928 if (MakeUpperCase(ch
) == firstChar
) {
930 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
931 ch
= CharAt(pos
+ posMatch
);
932 if (MakeUpperCase(ch
) != MakeUpperCase(s
[posMatch
]))
936 if ((!word
&& !wordStart
) ||
937 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
938 wordStart
&& IsWordStartAt(pos
))
945 // Ensure trying to match from start of character
946 pos
= MovePositionOutsideChar(pos
, increment
, false);
950 //Platform::DebugPrintf("Not found\n");
954 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
957 delete []substituted
;
959 DocumentIndexer
di(this, Length());
960 if (!pre
->GrabMatches(di
))
962 unsigned int lenResult
= 0;
963 for (int i
= 0; i
< *length
; i
++) {
964 if ((text
[i
] == '\\') && (text
[i
+ 1] >= '1' && text
[i
+ 1] <= '9')) {
965 unsigned int patNum
= text
[i
+ 1] - '0';
966 lenResult
+= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
972 substituted
= new char[lenResult
+ 1];
975 char *o
= substituted
;
976 for (int j
= 0; j
< *length
; j
++) {
977 if ((text
[j
] == '\\') && (text
[j
+ 1] >= '1' && text
[j
+ 1] <= '9')) {
978 unsigned int patNum
= text
[j
+ 1] - '0';
979 unsigned int len
= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
980 if (pre
->pat
[patNum
]) // Will be null if try for a match that did not occur
981 memcpy(o
, pre
->pat
[patNum
], len
);
993 int Document::LinesTotal() {
997 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
998 for (int pos
= r
.start
; pos
< r
.end
; pos
++) {
999 char ch
= CharAt(pos
);
1000 if (dbcsCodePage
&& IsDBCS(pos
)) {
1001 pos
+= LenChar(pos
);
1003 if (makeUpperCase
) {
1005 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1009 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1016 void Document::SetWordChars(unsigned char *chars
) {
1018 for (ch
= 0; ch
< 256; ch
++) {
1019 wordchars
[ch
] = false;
1023 wordchars
[*chars
] = true;
1027 for (ch
= 0; ch
< 256; ch
++) {
1028 wordchars
[ch
] = isalnum(ch
) || ch
== '_';
1033 void Document::SetStylingBits(int bits
) {
1035 stylingBitsMask
= 0;
1036 for (int bit
= 0; bit
< stylingBits
; bit
++) {
1037 stylingBitsMask
<<= 1;
1038 stylingBitsMask
|= 1;
1042 void Document::StartStyling(int position
, char mask
) {
1044 endStyled
= position
;
1047 void Document::SetStyleFor(int length
, char style
) {
1048 if (enteredCount
== 0) {
1050 int prevEndStyled
= endStyled
;
1051 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1052 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1053 prevEndStyled
, length
);
1056 endStyled
+= length
;
1061 void Document::SetStyles(int length
, char *styles
) {
1062 if (enteredCount
== 0) {
1064 int prevEndStyled
= endStyled
;
1065 bool didChange
= false;
1066 for (int iPos
= 0; iPos
< length
; iPos
++, endStyled
++) {
1067 if (cb
.SetStyleAt(endStyled
, styles
[iPos
], stylingMask
)) {
1072 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1073 prevEndStyled
, endStyled
- prevEndStyled
);
1080 bool Document::EnsureStyledTo(int pos
) {
1081 // Ask the watchers to style, and stop as soon as one responds.
1082 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++)
1083 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
1084 return pos
<= GetEndStyled();
1087 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
1088 for (int i
= 0; i
< lenWatchers
; i
++) {
1089 if ((watchers
[i
].watcher
== watcher
) &&
1090 (watchers
[i
].userData
== userData
))
1093 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1096 for (int j
= 0; j
< lenWatchers
; j
++)
1097 pwNew
[j
] = watchers
[j
];
1098 pwNew
[lenWatchers
].watcher
= watcher
;
1099 pwNew
[lenWatchers
].userData
= userData
;
1106 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
1107 for (int i
= 0; i
< lenWatchers
; i
++) {
1108 if ((watchers
[i
].watcher
== watcher
) &&
1109 (watchers
[i
].userData
== userData
)) {
1110 if (lenWatchers
== 1) {
1115 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1118 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1119 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1131 void Document::NotifyModifyAttempt() {
1132 for (int i
= 0; i
< lenWatchers
; i
++) {
1133 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1137 void Document::NotifySavePoint(bool atSavePoint
) {
1138 for (int i
= 0; i
< lenWatchers
; i
++) {
1139 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1143 void Document::NotifyModified(DocModification mh
) {
1144 for (int i
= 0; i
< lenWatchers
; i
++) {
1145 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1149 bool Document::IsWordPartSeparator(char ch
) {
1150 return ispunct(ch
) && IsWordChar(ch
);
1153 int Document::WordPartLeft(int pos
) {
1156 char startChar
= cb
.CharAt(pos
);
1157 if (IsWordPartSeparator(startChar
)) {
1158 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1163 startChar
= cb
.CharAt(pos
);
1165 if (islower(startChar
)) {
1166 while (pos
> 0 && islower(cb
.CharAt(pos
)))
1168 if (!isupper(cb
.CharAt(pos
)) && !islower(cb
.CharAt(pos
)))
1170 } else if (isupper(startChar
)) {
1171 while (pos
> 0 && isupper(cb
.CharAt(pos
)))
1173 if (!isupper(cb
.CharAt(pos
)))
1175 } else if (isdigit(startChar
)) {
1176 while (pos
> 0 && isdigit(cb
.CharAt(pos
)))
1178 if (!isdigit(cb
.CharAt(pos
)))
1180 } else if (ispunct(startChar
)) {
1181 while (pos
> 0 && ispunct(cb
.CharAt(pos
)))
1183 if (!ispunct(cb
.CharAt(pos
)))
1185 } else if (isspacechar(startChar
)) {
1186 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1188 if (!isspacechar(cb
.CharAt(pos
)))
1196 int Document::WordPartRight(int pos
) {
1197 char startChar
= cb
.CharAt(pos
);
1198 int length
= Length();
1199 if (IsWordPartSeparator(startChar
)) {
1200 while (pos
< length
&& IsWordPartSeparator(cb
.CharAt(pos
)))
1202 startChar
= cb
.CharAt(pos
);
1204 if (islower(startChar
)) {
1205 while (pos
< length
&& islower(cb
.CharAt(pos
)))
1207 } else if (isupper(startChar
)) {
1208 if (islower(cb
.CharAt(pos
+ 1))) {
1210 while (pos
< length
&& islower(cb
.CharAt(pos
)))
1213 while (pos
< length
&& isupper(cb
.CharAt(pos
)))
1216 if (islower(cb
.CharAt(pos
)) && isupper(cb
.CharAt(pos
- 1)))
1218 } else if (isdigit(startChar
)) {
1219 while (pos
< length
&& isdigit(cb
.CharAt(pos
)))
1221 } else if (ispunct(startChar
)) {
1222 while (pos
< length
&& ispunct(cb
.CharAt(pos
)))
1224 } else if (isspacechar(startChar
)) {
1225 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))