1 // Scintilla source code edit control
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
5 // Copyright 1998-2003 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 static inline bool isspacechar(unsigned char ch
) {
23 return (ch
== ' ') || ((ch
>= 0x09) && (ch
<= 0x0d));
26 static inline bool IsPunctuation(char ch
) {
27 return isascii(ch
) && ispunct(ch
);
30 static inline bool IsADigit(char ch
) {
31 return isascii(ch
) && isdigit(ch
);
34 static inline bool IsLowerCase(char ch
) {
35 return isascii(ch
) && islower(ch
);
38 static inline bool IsUpperCase(char ch
) {
39 return isascii(ch
) && isupper(ch
);
42 Document::Document() {
47 eolMode
= SC_EOL_CRLF
;
51 stylingBitsMask
= 0x1F;
57 enteredReadOnlyCount
= 0;
62 backspaceUnindents
= false;
71 Document::~Document() {
72 for (int i
= 0; i
< lenWatchers
; i
++) {
73 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
84 // Increase reference count and return its previous value.
85 int Document::AddRef() {
89 // Decrease reference count and return its previous value.
90 // Delete the document if reference count reaches zero.
91 int Document::Release() {
92 int curRefCount
= --refCount
;
98 void Document::SetSavePoint() {
100 NotifySavePoint(true);
103 int Document::AddMark(int line
, int markerNum
) {
104 int prev
= cb
.AddMark(line
, markerNum
);
105 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
110 void Document::DeleteMark(int line
, int markerNum
) {
111 cb
.DeleteMark(line
, markerNum
);
112 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
116 void Document::DeleteMarkFromHandle(int markerHandle
) {
117 cb
.DeleteMarkFromHandle(markerHandle
);
118 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
122 void Document::DeleteAllMarks(int markerNum
) {
123 cb
.DeleteAllMarks(markerNum
);
124 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
128 int Document::LineStart(int line
) {
129 return cb
.LineStart(line
);
132 int Document::LineEnd(int line
) {
133 if (line
== LinesTotal() - 1) {
134 return LineStart(line
+ 1);
136 int position
= LineStart(line
+ 1) - 1;
137 // When line terminator is CR+LF, may need to go back one more
138 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
145 int Document::LineFromPosition(int pos
) {
146 return cb
.LineFromPosition(pos
);
149 int Document::LineEndPosition(int position
) {
150 return LineEnd(LineFromPosition(position
));
153 int Document::VCHomePosition(int position
) {
154 int line
= LineFromPosition(position
);
155 int startPosition
= LineStart(line
);
156 int endLine
= LineStart(line
+ 1) - 1;
157 int startText
= startPosition
;
158 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t' ) )
160 if (position
== startText
)
161 return startPosition
;
166 int Document::SetLevel(int line
, int level
) {
167 int prev
= cb
.SetLevel(line
, level
);
169 DocModification
mh(SC_MOD_CHANGEFOLD
| SC_MOD_CHANGEMARKER
,
170 LineStart(line
), 0, 0, 0);
172 mh
.foldLevelNow
= level
;
173 mh
.foldLevelPrev
= prev
;
179 static bool IsSubordinate(int levelStart
, int levelTry
) {
180 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
183 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
186 int Document::GetLastChild(int lineParent
, int level
) {
188 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
189 int maxLine
= LinesTotal();
190 int lineMaxSubord
= lineParent
;
191 while (lineMaxSubord
< maxLine
- 1) {
192 EnsureStyledTo(LineStart(lineMaxSubord
+ 2));
193 if (!IsSubordinate(level
, GetLevel(lineMaxSubord
+ 1)))
197 if (lineMaxSubord
> lineParent
) {
198 if (level
> (GetLevel(lineMaxSubord
+ 1) & SC_FOLDLEVELNUMBERMASK
)) {
199 // Have chewed up some whitespace that belongs to a parent so seek back
200 if (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
) {
205 return lineMaxSubord
;
208 int Document::GetFoldParent(int line
) {
209 int level
= GetLevel(line
);
210 int lineLook
= line
- 1;
211 while ((lineLook
> 0) && (
212 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
213 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
217 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
218 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
225 int Document::ClampPositionIntoDocument(int pos
) {
226 return Platform::Clamp(pos
, 0, Length());
229 bool Document::IsCrLf(int pos
) {
232 if (pos
>= (Length() - 1))
234 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
237 static const int maxBytesInDBCSCharacter
=5;
239 int Document::LenChar(int pos
) {
242 } else if (IsCrLf(pos
)) {
244 } else if (SC_CP_UTF8
== dbcsCodePage
) {
245 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
249 if (ch
>= (0x80 + 0x40 + 0x20))
251 int lengthDoc
= Length();
252 if ((pos
+ len
) > lengthDoc
)
253 return lengthDoc
-pos
;
256 } else if (dbcsCodePage
) {
257 char mbstr
[maxBytesInDBCSCharacter
+1];
259 for (i
=0; i
<Platform::DBCSCharMaxLength(); i
++) {
260 mbstr
[i
] = cb
.CharAt(pos
+i
);
263 return Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
269 // Normalise a position so that it is not halfway through a two byte character.
270 // This can occur in two situations -
271 // When lines are terminated with \r\n pairs which should be treated as one character.
272 // When displaying DBCS text such as Japanese.
273 // If moving, move the position in the indicated direction.
274 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
275 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
276 // If out of range, just return value - should be fixed up after
282 // Position 0 and Length() can not be between any two characters
288 // assert pos > 0 && pos < Length()
289 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
296 // Not between CR and LF
299 if (SC_CP_UTF8
== dbcsCodePage
) {
300 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
301 while ((pos
> 0) && (pos
< Length()) && (ch
>= 0x80) && (ch
< (0x80 + 0x40))) {
302 // ch is a trail byte
307 ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
310 // Anchor DBCS calculations at start of line because start of line can
311 // not be a DBCS trail byte.
314 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
316 while (startLine
< pos
) {
317 char mbstr
[maxBytesInDBCSCharacter
+1];
319 for(i
=0;i
<Platform::DBCSCharMaxLength();i
++) {
320 mbstr
[i
] = cb
.CharAt(startLine
+i
);
324 int mbsize
= Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
325 if (startLine
+ mbsize
== pos
) {
327 } else if (startLine
+ mbsize
> pos
) {
329 return startLine
+ mbsize
;
342 void Document::ModifiedAt(int pos
) {
347 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
348 // SetStyleAt does not change the persistent state of a document
350 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
351 bool Document::DeleteChars(int pos
, int len
) {
354 if ((pos
+ len
) > Length())
356 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
357 enteredReadOnlyCount
++;
358 NotifyModifyAttempt();
359 enteredReadOnlyCount
--;
361 if (enteredCount
!= 0) {
365 if (!cb
.IsReadOnly()) {
368 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
371 int prevLinesTotal
= LinesTotal();
372 bool startSavePoint
= cb
.IsSavePoint();
373 const char *text
= cb
.DeleteChars(pos
* 2, len
* 2);
374 if (startSavePoint
&& cb
.IsCollectingUndo())
375 NotifySavePoint(!startSavePoint
);
376 if ((pos
< Length()) || (pos
== 0))
382 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
,
384 LinesTotal() - prevLinesTotal
, text
));
388 return !cb
.IsReadOnly();
391 bool Document::InsertStyledString(int position
, char *s
, int insertLength
) {
392 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
393 enteredReadOnlyCount
++;
394 NotifyModifyAttempt();
395 enteredReadOnlyCount
--;
397 if (enteredCount
!= 0) {
401 if (!cb
.IsReadOnly()) {
404 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
405 position
/ 2, insertLength
/ 2,
407 int prevLinesTotal
= LinesTotal();
408 bool startSavePoint
= cb
.IsSavePoint();
409 const char *text
= cb
.InsertString(position
, s
, insertLength
);
410 if (startSavePoint
&& cb
.IsCollectingUndo())
411 NotifySavePoint(!startSavePoint
);
412 ModifiedAt(position
/ 2);
415 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
,
416 position
/ 2, insertLength
/ 2,
417 LinesTotal() - prevLinesTotal
, text
));
421 return !cb
.IsReadOnly();
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 bool Document::InsertChar(int pos
, char ch
) {
514 return InsertStyledString(pos
*2, chs
, 2);
517 // Insert a null terminated string
518 bool Document::InsertString(int position
, const char *s
) {
519 return InsertString(position
, s
, strlen(s
));
522 // Insert a string with a length
523 bool Document::InsertString(int position
, const char *s
, size_t insertLength
) {
524 bool changed
= false;
525 char *sWithStyle
= new char[insertLength
* 2];
527 for (size_t i
= 0; i
< insertLength
; i
++) {
528 sWithStyle
[i
*2] = s
[i
];
529 sWithStyle
[i
*2 + 1] = 0;
531 changed
= InsertStyledString(position
*2, sWithStyle
,
532 static_cast<int>(insertLength
*2));
538 void Document::ChangeChar(int pos
, char ch
) {
543 void Document::DelChar(int pos
) {
544 DeleteChars(pos
, LenChar(pos
));
547 void Document::DelCharBack(int pos
) {
550 } else if (IsCrLf(pos
- 2)) {
551 DeleteChars(pos
- 2, 2);
552 } else if (dbcsCodePage
) {
553 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
554 DeleteChars(startChar
, pos
- startChar
);
556 DeleteChars(pos
- 1, 1);
560 static bool isindentchar(char ch
) {
561 return (ch
== ' ') || (ch
== '\t');
564 static int NextTab(int pos
, int tabSize
) {
565 return ((pos
/ tabSize
) + 1) * tabSize
;
568 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
569 length
--; // ensure space for \0
571 while ((indent
>= tabSize
) && (length
> 0)) {
577 while ((indent
> 0) && (length
> 0)) {
585 int Document::GetLineIndentation(int line
) {
587 if ((line
>= 0) && (line
< LinesTotal())) {
588 int lineStart
= LineStart(line
);
589 int length
= Length();
590 for (int i
= lineStart
;i
< length
;i
++) {
591 char ch
= cb
.CharAt(i
);
595 indent
= NextTab(indent
, tabInChars
);
603 void Document::SetLineIndentation(int line
, int indent
) {
604 int indentOfLine
= GetLineIndentation(line
);
607 if (indent
!= indentOfLine
) {
609 CreateIndentation(linebuf
, sizeof(linebuf
), indent
, tabInChars
, !useTabs
);
610 int thisLineStart
= LineStart(line
);
611 int indentPos
= GetLineIndentPosition(line
);
612 DeleteChars(thisLineStart
, indentPos
- thisLineStart
);
613 InsertString(thisLineStart
, linebuf
);
617 int Document::GetLineIndentPosition(int line
) {
620 int pos
= LineStart(line
);
621 int length
= Length();
622 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
628 int Document::GetColumn(int pos
) {
630 int line
= LineFromPosition(pos
);
631 if ((line
>= 0) && (line
< LinesTotal())) {
632 for (int i
= LineStart(line
);i
< pos
;) {
633 char ch
= cb
.CharAt(i
);
635 column
= NextTab(column
, tabInChars
);
637 } else if (ch
== '\r') {
639 } else if (ch
== '\n') {
643 i
= MovePositionOutsideChar(i
+ 1, 1);
650 int Document::FindColumn(int line
, int column
) {
651 int position
= LineStart(line
);
652 int columnCurrent
= 0;
653 if ((line
>= 0) && (line
< LinesTotal())) {
654 while (columnCurrent
< column
) {
655 char ch
= cb
.CharAt(position
);
657 columnCurrent
= NextTab(columnCurrent
, tabInChars
);
659 } else if (ch
== '\r') {
661 } else if (ch
== '\n') {
665 position
= MovePositionOutsideChar(position
+ 1, 1);
672 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
673 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
674 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
675 int indentOfLine
= GetLineIndentation(line
);
677 SetLineIndentation(line
, indentOfLine
+ IndentSize());
679 SetLineIndentation(line
, indentOfLine
- IndentSize());
683 void Document::ConvertLineEnds(int eolModeSet
) {
685 for (int pos
= 0; pos
< Length(); pos
++) {
686 if (cb
.CharAt(pos
) == '\r') {
687 if (cb
.CharAt(pos
+ 1) == '\n') {
688 if (eolModeSet
!= SC_EOL_CRLF
) {
690 if (eolModeSet
== SC_EOL_CR
)
691 InsertString(pos
, "\r", 1);
693 InsertString(pos
, "\n", 1);
698 if (eolModeSet
!= SC_EOL_CR
) {
700 if (eolModeSet
== SC_EOL_CRLF
) {
701 InsertString(pos
, "\r\n", 2);
704 InsertString(pos
, "\n", 1);
708 } else if (cb
.CharAt(pos
) == '\n') {
709 if (eolModeSet
!= SC_EOL_LF
) {
711 if (eolModeSet
== SC_EOL_CRLF
) {
712 InsertString(pos
, "\r\n", 2);
715 InsertString(pos
, "\r", 1);
723 int Document::ParaDown(int pos
) {
724 int line
= LineFromPosition(pos
);
725 while (line
< LinesTotal() && LineStart(line
) != LineEnd(line
)) { // skip non-empty lines
728 while (line
< LinesTotal() && LineStart(line
) == LineEnd(line
)) { // skip empty lines
731 if (line
< LinesTotal())
732 return LineStart(line
);
733 else // end of a document
734 return LineEnd(line
-1);
737 int Document::ParaUp(int pos
) {
738 int line
= LineFromPosition(pos
);
740 while (line
>= 0 && LineStart(line
) == LineEnd(line
)) { // skip empty lines
743 while (line
>= 0 && LineStart(line
) != LineEnd(line
)) { // skip non-empty lines
747 return LineStart(line
);
750 Document::charClassification
Document::WordCharClass(unsigned char ch
) {
751 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
>= 0x80))
753 return charClass
[ch
];
757 * Used by commmands that want to select whole words.
758 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
760 int Document::ExtendWordSelect(int pos
, int delta
, bool onlyWordCharacters
) {
761 charClassification ccStart
= ccWord
;
763 if (!onlyWordCharacters
)
764 ccStart
= WordCharClass(cb
.CharAt(pos
-1));
765 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
))
768 if (!onlyWordCharacters
)
769 ccStart
= WordCharClass(cb
.CharAt(pos
));
770 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
777 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
779 * This is looking for a transition between character classes although there is also some
780 * additional movement to transit white space.
781 * Used by cursor movement by word commands.
783 int Document::NextWordStart(int pos
, int delta
) {
785 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccSpace
))
788 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
-1));
789 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
)) {
794 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
));
795 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
797 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccSpace
))
804 * Check that the character at the given position is a word or punctuation character and that
805 * the previous character is of a different character class.
807 bool Document::IsWordStartAt(int pos
) {
809 charClassification ccPos
= WordCharClass(CharAt(pos
));
810 return (ccPos
== ccWord
|| ccPos
== ccPunctuation
) &&
811 (ccPos
!= WordCharClass(CharAt(pos
- 1)));
817 * Check that the character at the given position is a word or punctuation character and that
818 * the next character is of a different character class.
820 bool Document::IsWordEndAt(int pos
) {
821 if (pos
< Length() - 1) {
822 charClassification ccPrev
= WordCharClass(CharAt(pos
-1));
823 return (ccPrev
== ccWord
|| ccPrev
== ccPunctuation
) &&
824 (ccPrev
!= WordCharClass(CharAt(pos
)));
830 * Check that the given range is has transitions between character classes at both
831 * ends and where the characters on the inside are word or punctuation characters.
833 bool Document::IsWordAt(int start
, int end
) {
834 return IsWordStartAt(start
) && IsWordEndAt(end
);
837 // The comparison and case changing functions here assume ASCII
838 // or extended ASCII such as the normal Windows code page.
840 static inline char MakeUpperCase(char ch
) {
841 if (ch
< 'a' || ch
> 'z')
844 return static_cast<char>(ch
- 'a' + 'A');
847 static inline char MakeLowerCase(char ch
) {
848 if (ch
< 'A' || ch
> 'Z')
851 return static_cast<char>(ch
- 'A' + 'a');
854 // Define a way for the Regular Expression code to access the document
855 class DocumentIndexer
: public CharacterIndexer
{
859 DocumentIndexer(Document
*pdoc_
, int end_
) :
860 pdoc(pdoc_
), end(end_
) {
863 virtual char CharAt(int index
) {
864 if (index
< 0 || index
>= end
)
867 return pdoc
->CharAt(index
);
872 * Find text in document, supporting both forward and backward
873 * searches (just pass minPos > maxPos to do a backward search)
874 * Has not been tested with backwards DBCS searches yet.
876 long Document::FindText(int minPos
, int maxPos
, const char *s
,
877 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
, bool posix
,
881 pre
= new RESearch();
885 int increment
= (minPos
<= maxPos
) ? 1 : -1;
887 int startPos
= minPos
;
890 // Range endpoints should not be inside DBCS characters, but just in case, move them.
891 startPos
= MovePositionOutsideChar(startPos
, 1, false);
892 endPos
= MovePositionOutsideChar(endPos
, 1, false);
894 const char *errmsg
= pre
->Compile(s
, *length
, caseSensitive
, posix
);
898 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
899 // Replace first '.' with '-' in each property file variable reference:
900 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
902 int lineRangeStart
= LineFromPosition(startPos
);
903 int lineRangeEnd
= LineFromPosition(endPos
);
904 if ((increment
== 1) &&
905 (startPos
>= LineEnd(lineRangeStart
)) &&
906 (lineRangeStart
< lineRangeEnd
)) {
907 // the start position is at end of line or between line end characters.
909 startPos
= LineStart(lineRangeStart
);
913 char searchEnd
= s
[*length
- 1];
914 int lineRangeBreak
= lineRangeEnd
+ increment
;
915 for (int line
= lineRangeStart
; line
!= lineRangeBreak
; line
+= increment
) {
916 int startOfLine
= LineStart(line
);
917 int endOfLine
= LineEnd(line
);
918 if (increment
== 1) {
919 if (line
== lineRangeStart
) {
920 if ((startPos
!= startOfLine
) && (s
[0] == '^'))
921 continue; // Can't match start of line if start position after start of line
922 startOfLine
= startPos
;
924 if (line
== lineRangeEnd
) {
925 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
926 continue; // Can't match end of line if end position before end of line
930 if (line
== lineRangeEnd
) {
931 if ((endPos
!= startOfLine
) && (s
[0] == '^'))
932 continue; // Can't match start of line if end position after start of line
933 startOfLine
= endPos
;
935 if (line
== lineRangeStart
) {
936 if ((startPos
!= endOfLine
) && (searchEnd
== '$'))
937 continue; // Can't match end of line if start position before end of line
938 endOfLine
= startPos
;
942 DocumentIndexer
di(this, endOfLine
);
943 int success
= pre
->Execute(di
, startOfLine
, endOfLine
);
946 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
947 if (increment
== -1) {
948 // Check for the last match on this line.
949 int repetitions
= 1000; // Break out of infinite loop
950 while (success
&& (pre
->eopat
[0] < endOfLine
) && (repetitions
--)) {
951 success
= pre
->Execute(di
, pre
->eopat
[0], endOfLine
);
953 if (pre
->eopat
[0] <= minPos
) {
955 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
970 bool forward
= minPos
<= maxPos
;
971 int increment
= forward
? 1 : -1;
973 // Range endpoints should not be inside DBCS characters, but just in case, move them.
974 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
975 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
977 // Compute actual search ranges needed
978 int lengthFind
= *length
;
979 if (lengthFind
== -1)
980 lengthFind
= static_cast<int>(strlen(s
));
981 int endSearch
= endPos
;
982 if (startPos
<= endPos
) {
983 endSearch
= endPos
- lengthFind
+ 1;
985 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
986 char firstChar
= s
[0];
988 firstChar
= static_cast<char>(MakeUpperCase(firstChar
));
990 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
991 char ch
= CharAt(pos
);
993 if (ch
== firstChar
) {
995 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
996 ch
= CharAt(pos
+ posMatch
);
997 if (ch
!= s
[posMatch
])
1001 if ((!word
&& !wordStart
) ||
1002 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
1003 wordStart
&& IsWordStartAt(pos
))
1008 if (MakeUpperCase(ch
) == firstChar
) {
1010 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
1011 ch
= CharAt(pos
+ posMatch
);
1012 if (MakeUpperCase(ch
) != MakeUpperCase(s
[posMatch
]))
1016 if ((!word
&& !wordStart
) ||
1017 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
1018 wordStart
&& IsWordStartAt(pos
))
1025 // Ensure trying to match from start of character
1026 pos
= MovePositionOutsideChar(pos
, increment
, false);
1030 //Platform::DebugPrintf("Not found\n");
1034 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
1037 delete []substituted
;
1039 DocumentIndexer
di(this, Length());
1040 if (!pre
->GrabMatches(di
))
1042 unsigned int lenResult
= 0;
1043 for (int i
= 0; i
< *length
; i
++) {
1044 if ((text
[i
] == '\\') && (text
[i
+ 1] >= '1' && text
[i
+ 1] <= '9')) {
1045 unsigned int patNum
= text
[i
+ 1] - '0';
1046 lenResult
+= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1052 substituted
= new char[lenResult
+ 1];
1055 char *o
= substituted
;
1056 for (int j
= 0; j
< *length
; j
++) {
1057 if ((text
[j
] == '\\') && (text
[j
+ 1] >= '1' && text
[j
+ 1] <= '9')) {
1058 unsigned int patNum
= text
[j
+ 1] - '0';
1059 unsigned int len
= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1060 if (pre
->pat
[patNum
]) // Will be null if try for a match that did not occur
1061 memcpy(o
, pre
->pat
[patNum
], len
);
1069 *length
= lenResult
;
1073 int Document::LinesTotal() {
1077 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
1078 for (int pos
= r
.start
; pos
< r
.end
; pos
++) {
1079 int len
= LenChar(pos
);
1080 if (dbcsCodePage
&& (len
> 1)) {
1083 char ch
= CharAt(pos
);
1084 if (makeUpperCase
) {
1085 if (IsLowerCase(ch
)) {
1086 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1089 if (IsUpperCase(ch
)) {
1090 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1097 void Document::SetWordChars(unsigned char *chars
) {
1099 for (ch
= 0; ch
< 256; ch
++) {
1100 if (ch
== '\r' || ch
== '\n')
1101 charClass
[ch
] = ccNewLine
;
1102 else if (ch
< 0x20 || ch
== ' ')
1103 charClass
[ch
] = ccSpace
;
1105 charClass
[ch
] = ccPunctuation
;
1109 charClass
[*chars
] = ccWord
;
1113 for (ch
= 0; ch
< 256; ch
++) {
1114 if (ch
>= 0x80 || isalnum(ch
) || ch
== '_')
1115 charClass
[ch
] = ccWord
;
1120 void Document::SetStylingBits(int bits
) {
1122 stylingBitsMask
= 0;
1123 for (int bit
= 0; bit
< stylingBits
; bit
++) {
1124 stylingBitsMask
<<= 1;
1125 stylingBitsMask
|= 1;
1129 void Document::StartStyling(int position
, char mask
) {
1131 endStyled
= position
;
1134 bool Document::SetStyleFor(int length
, char style
) {
1135 if (enteredCount
!= 0) {
1139 style
&= stylingMask
;
1140 int prevEndStyled
= endStyled
;
1141 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1142 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1143 prevEndStyled
, length
);
1146 endStyled
+= length
;
1152 bool Document::SetStyles(int length
, char *styles
) {
1153 if (enteredCount
!= 0) {
1157 int prevEndStyled
= endStyled
;
1158 bool didChange
= false;
1160 for (int iPos
= 0; iPos
< length
; iPos
++, endStyled
++) {
1161 PLATFORM_ASSERT(endStyled
< Length());
1162 if (cb
.SetStyleAt(endStyled
, styles
[iPos
], stylingMask
)) {
1168 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1169 prevEndStyled
, lastChange
);
1177 bool Document::EnsureStyledTo(int pos
) {
1178 if (pos
> GetEndStyled()) {
1180 if (styleClock
> 0x100000) {
1183 // Ask the watchers to style, and stop as soon as one responds.
1184 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++) {
1185 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
1188 return pos
<= GetEndStyled();
1191 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
1192 for (int i
= 0; i
< lenWatchers
; i
++) {
1193 if ((watchers
[i
].watcher
== watcher
) &&
1194 (watchers
[i
].userData
== userData
))
1197 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1200 for (int j
= 0; j
< lenWatchers
; j
++)
1201 pwNew
[j
] = watchers
[j
];
1202 pwNew
[lenWatchers
].watcher
= watcher
;
1203 pwNew
[lenWatchers
].userData
= userData
;
1210 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
1211 for (int i
= 0; i
< lenWatchers
; i
++) {
1212 if ((watchers
[i
].watcher
== watcher
) &&
1213 (watchers
[i
].userData
== userData
)) {
1214 if (lenWatchers
== 1) {
1219 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1222 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1223 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1235 void Document::NotifyModifyAttempt() {
1236 for (int i
= 0; i
< lenWatchers
; i
++) {
1237 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1241 void Document::NotifySavePoint(bool atSavePoint
) {
1242 for (int i
= 0; i
< lenWatchers
; i
++) {
1243 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1247 void Document::NotifyModified(DocModification mh
) {
1248 for (int i
= 0; i
< lenWatchers
; i
++) {
1249 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1253 bool Document::IsWordPartSeparator(char ch
) {
1254 return (WordCharClass(ch
) == ccWord
) && IsPunctuation(ch
);
1257 int Document::WordPartLeft(int pos
) {
1260 char startChar
= cb
.CharAt(pos
);
1261 if (IsWordPartSeparator(startChar
)) {
1262 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1267 startChar
= cb
.CharAt(pos
);
1269 if (IsLowerCase(startChar
)) {
1270 while (pos
> 0 && IsLowerCase(cb
.CharAt(pos
)))
1272 if (!IsUpperCase(cb
.CharAt(pos
)) && !IsLowerCase(cb
.CharAt(pos
)))
1274 } else if (IsUpperCase(startChar
)) {
1275 while (pos
> 0 && IsUpperCase(cb
.CharAt(pos
)))
1277 if (!IsUpperCase(cb
.CharAt(pos
)))
1279 } else if (IsADigit(startChar
)) {
1280 while (pos
> 0 && IsADigit(cb
.CharAt(pos
)))
1282 if (!IsADigit(cb
.CharAt(pos
)))
1284 } else if (IsPunctuation(startChar
)) {
1285 while (pos
> 0 && IsPunctuation(cb
.CharAt(pos
)))
1287 if (!IsPunctuation(cb
.CharAt(pos
)))
1289 } else if (isspacechar(startChar
)) {
1290 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1292 if (!isspacechar(cb
.CharAt(pos
)))
1294 } else if (!isascii(startChar
)) {
1295 while (pos
> 0 && !isascii(cb
.CharAt(pos
)))
1297 if (isascii(cb
.CharAt(pos
)))
1307 int Document::WordPartRight(int pos
) {
1308 char startChar
= cb
.CharAt(pos
);
1309 int length
= Length();
1310 if (IsWordPartSeparator(startChar
)) {
1311 while (pos
< length
&& IsWordPartSeparator(cb
.CharAt(pos
)))
1313 startChar
= cb
.CharAt(pos
);
1315 if (!isascii(startChar
)) {
1316 while (pos
< length
&& !isascii(cb
.CharAt(pos
)))
1318 } else if (IsLowerCase(startChar
)) {
1319 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1321 } else if (IsUpperCase(startChar
)) {
1322 if (IsLowerCase(cb
.CharAt(pos
+ 1))) {
1324 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1327 while (pos
< length
&& IsUpperCase(cb
.CharAt(pos
)))
1330 if (IsLowerCase(cb
.CharAt(pos
)) && IsUpperCase(cb
.CharAt(pos
- 1)))
1332 } else if (IsADigit(startChar
)) {
1333 while (pos
< length
&& IsADigit(cb
.CharAt(pos
)))
1335 } else if (IsPunctuation(startChar
)) {
1336 while (pos
< length
&& IsPunctuation(cb
.CharAt(pos
)))
1338 } else if (isspacechar(startChar
)) {
1339 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))
1347 int Document::ExtendStyleRange(int pos
, int delta
) {
1348 int sStart
= cb
.StyleAt(pos
);
1350 while (pos
> 0 && (cb
.StyleAt(pos
) == sStart
))
1354 while (pos
< (Length()) && (cb
.StyleAt(pos
) == sStart
))