1 // Scintilla source code edit control
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
5 // Copyright 1998-2002 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 Document::Document() {
31 eolMode
= SC_EOL_CRLF
;
35 stylingBitsMask
= 0x1F;
41 enteredReadOnlyCount
= 0;
46 backspaceUnindents
= false;
55 Document::~Document() {
56 for (int i
= 0; i
< lenWatchers
; i
++) {
57 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
68 // Increase reference count and return its previous value.
69 int Document::AddRef() {
73 // Decrease reference count and return its previous value.
74 // Delete the document if reference count reaches zero.
75 int Document::Release() {
76 int curRefCount
= --refCount
;
82 void Document::SetSavePoint() {
84 NotifySavePoint(true);
87 int Document::AddMark(int line
, int markerNum
) {
88 int prev
= cb
.AddMark(line
, markerNum
);
89 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
94 void Document::DeleteMark(int line
, int markerNum
) {
95 cb
.DeleteMark(line
, markerNum
);
96 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
100 void Document::DeleteMarkFromHandle(int markerHandle
) {
101 cb
.DeleteMarkFromHandle(markerHandle
);
102 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
106 void Document::DeleteAllMarks(int markerNum
) {
107 cb
.DeleteAllMarks(markerNum
);
108 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
112 int Document::LineStart(int line
) {
113 return cb
.LineStart(line
);
116 int Document::LineEnd(int line
) {
117 if (line
== LinesTotal() - 1) {
118 return LineStart(line
+ 1);
120 int position
= LineStart(line
+ 1) - 1;
121 // When line terminator is CR+LF, may need to go back one more
122 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
129 int Document::LineFromPosition(int pos
) {
130 return cb
.LineFromPosition(pos
);
133 int Document::LineEndPosition(int position
) {
134 return LineEnd(LineFromPosition(position
));
137 int Document::VCHomePosition(int position
) {
138 int line
= LineFromPosition(position
);
139 int startPosition
= LineStart(line
);
140 int endLine
= LineStart(line
+ 1) - 1;
141 int startText
= startPosition
;
142 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t' ) )
144 if (position
== startText
)
145 return startPosition
;
150 int Document::SetLevel(int line
, int level
) {
151 int prev
= cb
.SetLevel(line
, level
);
153 DocModification
mh(SC_MOD_CHANGEFOLD
| SC_MOD_CHANGEMARKER
,
154 LineStart(line
), 0, 0, 0);
156 mh
.foldLevelNow
= level
;
157 mh
.foldLevelPrev
= prev
;
163 static bool IsSubordinate(int levelStart
, int levelTry
) {
164 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
167 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
170 int Document::GetLastChild(int lineParent
, int level
) {
172 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
173 int maxLine
= LinesTotal();
174 int lineMaxSubord
= lineParent
;
175 while (lineMaxSubord
< maxLine
- 1) {
176 EnsureStyledTo(LineStart(lineMaxSubord
+ 2));
177 if (!IsSubordinate(level
, GetLevel(lineMaxSubord
+ 1)))
181 if (lineMaxSubord
> lineParent
) {
182 if (level
> (GetLevel(lineMaxSubord
+ 1) & SC_FOLDLEVELNUMBERMASK
)) {
183 // Have chewed up some whitespace that belongs to a parent so seek back
184 if (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
) {
189 return lineMaxSubord
;
192 int Document::GetFoldParent(int line
) {
193 int level
= GetLevel(line
);
194 int lineLook
= line
- 1;
195 while ((lineLook
> 0) && (
196 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
197 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
201 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
202 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
209 int Document::ClampPositionIntoDocument(int pos
) {
210 return Platform::Clamp(pos
, 0, Length());
213 bool Document::IsCrLf(int pos
) {
216 if (pos
>= (Length() - 1))
218 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
221 bool Document::IsDBCS(int pos
) {
223 if (SC_CP_UTF8
== dbcsCodePage
) {
224 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
227 // Anchor DBCS calculations at start of line because start of line can
228 // not be a DBCS trail byte.
230 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
232 while (startLine
<= pos
) {
233 if (Platform::IsDBCSLeadByte(dbcsCodePage
, cb
.CharAt(startLine
))) {
235 if (startLine
>= pos
)
245 int Document::LenChar(int pos
) {
248 } else if (SC_CP_UTF8
== dbcsCodePage
) {
249 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
253 if (ch
>= (0x80 + 0x40 + 0x20))
255 int lengthDoc
= Length();
256 if ((pos
+ len
) > lengthDoc
)
257 return lengthDoc
-pos
;
260 } else if (IsDBCS(pos
)) {
267 // Normalise a position so that it is not halfway through a two byte character.
268 // This can occur in two situations -
269 // When lines are terminated with \r\n pairs which should be treated as one character.
270 // When displaying DBCS text such as Japanese.
271 // If moving, move the position in the indicated direction.
272 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
273 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
274 // If out of range, just return value - should be fixed up after
280 // Position 0 and Length() can not be between any two characters
286 // assert pos > 0 && pos < Length()
287 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
294 // Not between CR and LF
297 if (SC_CP_UTF8
== dbcsCodePage
) {
298 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
299 while ((pos
> 0) && (pos
< Length()) && (ch
>= 0x80) && (ch
< (0x80 + 0x40))) {
300 // ch is a trail byte
305 ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
308 // Anchor DBCS calculations at start of line because start of line can
309 // not be a DBCS trail byte.
311 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
313 bool atLeadByte
= false;
314 while (startLine
< pos
) {
317 else if (Platform::IsDBCSLeadByte(dbcsCodePage
, cb
.CharAt(startLine
)))
326 // Position is between a lead byte and a trail byte
338 void Document::ModifiedAt(int pos
) {
343 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
344 // SetStyleAt does not change the persistent state of a document
346 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
347 bool Document::DeleteChars(int pos
, int len
) {
350 if ((pos
+ len
) > Length())
352 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
353 enteredReadOnlyCount
++;
354 NotifyModifyAttempt();
355 enteredReadOnlyCount
--;
357 if (enteredCount
!= 0) {
361 if (!cb
.IsReadOnly()) {
364 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
367 int prevLinesTotal
= LinesTotal();
368 bool startSavePoint
= cb
.IsSavePoint();
369 const char *text
= cb
.DeleteChars(pos
* 2, len
* 2);
370 if (startSavePoint
&& cb
.IsCollectingUndo())
371 NotifySavePoint(!startSavePoint
);
372 if ((pos
< Length()) || (pos
== 0))
378 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
,
380 LinesTotal() - prevLinesTotal
, text
));
384 return !cb
.IsReadOnly();
387 bool Document::InsertStyledString(int position
, char *s
, int insertLength
) {
388 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
389 enteredReadOnlyCount
++;
390 NotifyModifyAttempt();
391 enteredReadOnlyCount
--;
393 if (enteredCount
!= 0) {
397 if (!cb
.IsReadOnly()) {
400 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
401 position
/ 2, insertLength
/ 2,
403 int prevLinesTotal
= LinesTotal();
404 bool startSavePoint
= cb
.IsSavePoint();
405 const char *text
= cb
.InsertString(position
, s
, insertLength
);
406 if (startSavePoint
&& cb
.IsCollectingUndo())
407 NotifySavePoint(!startSavePoint
);
408 ModifiedAt(position
/ 2);
411 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
,
412 position
/ 2, insertLength
/ 2,
413 LinesTotal() - prevLinesTotal
, text
));
417 return !cb
.IsReadOnly();
420 int Document::Undo() {
422 if (enteredCount
== 0) {
424 bool startSavePoint
= cb
.IsSavePoint();
425 int steps
= cb
.StartUndo();
426 //Platform::DebugPrintf("Steps=%d\n", steps);
427 for (int step
= 0; step
< steps
; step
++) {
428 int prevLinesTotal
= LinesTotal();
429 const Action
&action
= cb
.GetUndoStep();
430 if (action
.at
== removeAction
) {
431 NotifyModified(DocModification(
432 SC_MOD_BEFOREINSERT
| SC_PERFORMED_UNDO
, action
));
434 NotifyModified(DocModification(
435 SC_MOD_BEFOREDELETE
| SC_PERFORMED_UNDO
, action
));
437 cb
.PerformUndoStep();
438 int cellPosition
= action
.position
/ 2;
439 ModifiedAt(cellPosition
);
440 newPos
= cellPosition
;
442 int modFlags
= SC_PERFORMED_UNDO
;
443 // With undo, an insertion action becomes a deletion notification
444 if (action
.at
== removeAction
) {
445 newPos
+= action
.lenData
;
446 modFlags
|= SC_MOD_INSERTTEXT
;
448 modFlags
|= SC_MOD_DELETETEXT
;
450 if (step
== steps
- 1)
451 modFlags
|= SC_LASTSTEPINUNDOREDO
;
452 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
453 LinesTotal() - prevLinesTotal
, action
.data
));
456 bool endSavePoint
= cb
.IsSavePoint();
457 if (startSavePoint
!= endSavePoint
)
458 NotifySavePoint(endSavePoint
);
464 int Document::Redo() {
466 if (enteredCount
== 0) {
468 bool startSavePoint
= cb
.IsSavePoint();
469 int steps
= cb
.StartRedo();
470 for (int step
= 0; step
< steps
; step
++) {
471 int prevLinesTotal
= LinesTotal();
472 const Action
&action
= cb
.GetRedoStep();
473 if (action
.at
== insertAction
) {
474 NotifyModified(DocModification(
475 SC_MOD_BEFOREINSERT
| SC_PERFORMED_REDO
, action
));
477 NotifyModified(DocModification(
478 SC_MOD_BEFOREDELETE
| SC_PERFORMED_REDO
, action
));
480 cb
.PerformRedoStep();
481 ModifiedAt(action
.position
/ 2);
482 newPos
= action
.position
/ 2;
484 int modFlags
= SC_PERFORMED_REDO
;
485 if (action
.at
== insertAction
) {
486 newPos
+= action
.lenData
;
487 modFlags
|= SC_MOD_INSERTTEXT
;
489 modFlags
|= SC_MOD_DELETETEXT
;
491 if (step
== steps
- 1)
492 modFlags
|= SC_LASTSTEPINUNDOREDO
;
494 DocModification(modFlags
, action
.position
/ 2, action
.lenData
,
495 LinesTotal() - prevLinesTotal
, action
.data
));
498 bool endSavePoint
= cb
.IsSavePoint();
499 if (startSavePoint
!= endSavePoint
)
500 NotifySavePoint(endSavePoint
);
506 bool Document::InsertChar(int pos
, char ch
) {
510 return InsertStyledString(pos
*2, chs
, 2);
513 // Insert a null terminated string
514 bool Document::InsertString(int position
, const char *s
) {
515 return InsertString(position
, s
, strlen(s
));
518 // Insert a string with a length
519 bool Document::InsertString(int position
, const char *s
, size_t insertLength
) {
520 bool changed
= false;
521 char *sWithStyle
= new char[insertLength
* 2];
523 for (size_t i
= 0; i
< insertLength
; i
++) {
524 sWithStyle
[i
*2] = s
[i
];
525 sWithStyle
[i
*2 + 1] = 0;
527 changed
= InsertStyledString(position
*2, sWithStyle
,
528 static_cast<int>(insertLength
*2));
534 void Document::ChangeChar(int pos
, char ch
) {
539 void Document::DelChar(int pos
) {
540 DeleteChars(pos
, LenChar(pos
));
543 void Document::DelCharBack(int pos
) {
546 } else if (IsCrLf(pos
- 2)) {
547 DeleteChars(pos
- 2, 2);
548 } else if (SC_CP_UTF8
== dbcsCodePage
) {
549 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
550 DeleteChars(startChar
, pos
- startChar
);
551 } else if (IsDBCS(pos
- 1)) {
552 DeleteChars(pos
- 2, 2);
554 DeleteChars(pos
- 1, 1);
558 static bool isindentchar(char ch
) {
559 return (ch
== ' ') || (ch
== '\t');
562 static int NextTab(int pos
, int tabSize
) {
563 return ((pos
/ tabSize
) + 1) * tabSize
;
566 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
567 length
--; // ensure space for \0
569 while ((indent
>= tabSize
) && (length
> 0)) {
575 while ((indent
> 0) && (length
> 0)) {
583 int Document::GetLineIndentation(int line
) {
585 if ((line
>= 0) && (line
< LinesTotal())) {
586 int lineStart
= LineStart(line
);
587 int length
= Length();
588 for (int i
= lineStart
;i
< length
;i
++) {
589 char ch
= cb
.CharAt(i
);
593 indent
= NextTab(indent
, tabInChars
);
601 void Document::SetLineIndentation(int line
, int indent
) {
602 int indentOfLine
= GetLineIndentation(line
);
605 if (indent
!= indentOfLine
) {
607 CreateIndentation(linebuf
, sizeof(linebuf
), indent
, tabInChars
, !useTabs
);
608 int thisLineStart
= LineStart(line
);
609 int indentPos
= GetLineIndentPosition(line
);
610 DeleteChars(thisLineStart
, indentPos
- thisLineStart
);
611 InsertString(thisLineStart
, linebuf
);
615 int Document::GetLineIndentPosition(int line
) {
618 int pos
= LineStart(line
);
619 int length
= Length();
620 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
626 int Document::GetColumn(int pos
) {
628 int line
= LineFromPosition(pos
);
629 if ((line
>= 0) && (line
< LinesTotal())) {
630 for (int i
= LineStart(line
);i
< pos
;) {
631 char ch
= cb
.CharAt(i
);
633 column
= NextTab(column
, tabInChars
);
635 } else if (ch
== '\r') {
637 } else if (ch
== '\n') {
641 i
= MovePositionOutsideChar(i
+ 1, 1);
648 int Document::FindColumn(int line
, int column
) {
649 int position
= LineStart(line
);
650 int columnCurrent
= 0;
651 if ((line
>= 0) && (line
< LinesTotal())) {
652 while (columnCurrent
< column
) {
653 char ch
= cb
.CharAt(position
);
655 columnCurrent
= NextTab(columnCurrent
, tabInChars
);
657 } else if (ch
== '\r') {
659 } else if (ch
== '\n') {
663 position
= MovePositionOutsideChar(position
+ 1, 1);
670 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
671 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
672 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
673 int indentOfLine
= GetLineIndentation(line
);
675 SetLineIndentation(line
, indentOfLine
+ IndentSize());
677 SetLineIndentation(line
, indentOfLine
- IndentSize());
681 void Document::ConvertLineEnds(int eolModeSet
) {
683 for (int pos
= 0; pos
< Length(); pos
++) {
684 if (cb
.CharAt(pos
) == '\r') {
685 if (cb
.CharAt(pos
+ 1) == '\n') {
686 if (eolModeSet
!= SC_EOL_CRLF
) {
688 if (eolModeSet
== SC_EOL_CR
)
689 InsertString(pos
, "\r", 1);
691 InsertString(pos
, "\n", 1);
696 if (eolModeSet
!= SC_EOL_CR
) {
698 if (eolModeSet
== SC_EOL_CRLF
) {
699 InsertString(pos
, "\r\n", 2);
702 InsertString(pos
, "\n", 1);
706 } else if (cb
.CharAt(pos
) == '\n') {
707 if (eolModeSet
!= SC_EOL_LF
) {
709 if (eolModeSet
== SC_EOL_CRLF
) {
710 InsertString(pos
, "\r\n", 2);
713 InsertString(pos
, "\r", 1);
721 Document::charClassification
Document::WordCharClass(unsigned char ch
) {
722 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
>= 0x80))
724 return charClass
[ch
];
728 * Used by commmands that want to select whole words.
729 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
731 int Document::ExtendWordSelect(int pos
, int delta
, bool onlyWordCharacters
) {
732 charClassification ccStart
= ccWord
;
734 if (!onlyWordCharacters
)
735 ccStart
= WordCharClass(cb
.CharAt(pos
-1));
736 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
))
739 if (!onlyWordCharacters
)
740 ccStart
= WordCharClass(cb
.CharAt(pos
));
741 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
748 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
750 * This is looking for a transition between character classes although there is also some
751 * additional movement to transit white space.
752 * Used by cursor movement by word commands.
754 int Document::NextWordStart(int pos
, int delta
) {
756 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccSpace
))
759 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
-1));
760 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
)) {
765 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
));
766 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
768 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccSpace
))
775 * Check that the character at the given position is a word or punctuation character and that
776 * the previous character is of a different character class.
778 bool Document::IsWordStartAt(int pos
) {
780 charClassification ccPos
= WordCharClass(CharAt(pos
));
781 return (ccPos
== ccWord
|| ccPos
== ccPunctuation
) &&
782 (ccPos
!= WordCharClass(CharAt(pos
- 1)));
788 * Check that the character at the given position is a word or punctuation character and that
789 * the next character is of a different character class.
791 bool Document::IsWordEndAt(int pos
) {
792 if (pos
< Length() - 1) {
793 charClassification ccPrev
= WordCharClass(CharAt(pos
-1));
794 return (ccPrev
== ccWord
|| ccPrev
== ccPunctuation
) &&
795 (ccPrev
!= WordCharClass(CharAt(pos
)));
801 * Check that the given range is has transitions between character classes at both
802 * ends and where the characters on the inside are word or punctuation characters.
804 bool Document::IsWordAt(int start
, int end
) {
805 return IsWordStartAt(start
) && IsWordEndAt(end
);
808 // The comparison and case changing functions here assume ASCII
809 // or extended ASCII such as the normal Windows code page.
811 static inline char MakeUpperCase(char ch
) {
812 if (ch
< 'a' || ch
> 'z')
815 return static_cast<char>(ch
- 'a' + 'A');
818 static inline char MakeLowerCase(char ch
) {
819 if (ch
< 'A' || ch
> 'Z')
822 return static_cast<char>(ch
- 'A' + 'a');
825 // Define a way for the Regular Expression code to access the document
826 class DocumentIndexer
: public CharacterIndexer
{
830 DocumentIndexer(Document
*pdoc_
, int end_
) :
831 pdoc(pdoc_
), end(end_
) {
834 virtual char CharAt(int index
) {
835 if (index
< 0 || index
>= end
)
838 return pdoc
->CharAt(index
);
843 * Find text in document, supporting both forward and backward
844 * searches (just pass minPos > maxPos to do a backward search)
845 * Has not been tested with backwards DBCS searches yet.
847 long Document::FindText(int minPos
, int maxPos
, const char *s
,
848 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
,
852 pre
= new RESearch();
859 if (minPos
<= maxPos
) {
867 // Range endpoints should not be inside DBCS characters, but just in case, move them.
868 startPos
= MovePositionOutsideChar(startPos
, 1, false);
869 endPos
= MovePositionOutsideChar(endPos
, 1, false);
871 const char *errmsg
= pre
->Compile(s
, *length
, caseSensitive
);
875 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
876 // Replace first '.' with '-' in each property file variable reference:
877 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
879 int lineRangeStart
= LineFromPosition(startPos
);
880 int lineRangeEnd
= LineFromPosition(endPos
);
881 if ((startPos
>= LineEnd(lineRangeStart
)) && (lineRangeStart
< lineRangeEnd
)) {
882 // the start position is at end of line or between line end characters.
884 startPos
= LineStart(lineRangeStart
);
888 char searchEnd
= s
[*length
- 1];
890 // These produce empty selections so nudge them on if needed
892 if (startPos
== LineStart(lineRangeStart
))
894 } else if (s
[0] == '$') {
895 if ((startPos
== LineEnd(lineRangeStart
)) && (lineRangeStart
< lineRangeEnd
))
896 startPos
= LineStart(lineRangeStart
+ 1);
898 lineRangeStart
= LineFromPosition(startPos
);
899 lineRangeEnd
= LineFromPosition(endPos
);
901 for (int line
= lineRangeStart
; line
<= lineRangeEnd
; line
++) {
902 int startOfLine
= LineStart(line
);
903 int endOfLine
= LineEnd(line
);
904 if (line
== lineRangeStart
) {
905 if ((startPos
!= startOfLine
) && (s
[0] == '^'))
906 continue; // Can't match start of line if start position after start of line
907 startOfLine
= startPos
;
909 if (line
== lineRangeEnd
) {
910 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
911 continue; // Can't match end of line if end position before end of line
914 DocumentIndexer
di(this, endOfLine
);
915 int success
= pre
->Execute(di
, startOfLine
, endOfLine
);
918 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
927 bool forward
= minPos
<= maxPos
;
928 int increment
= forward
? 1 : -1;
930 // Range endpoints should not be inside DBCS characters, but just in case, move them.
931 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
932 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
934 // Compute actual search ranges needed
935 int lengthFind
= *length
;
936 if (lengthFind
== -1)
937 lengthFind
= static_cast<int>(strlen(s
));
938 int endSearch
= endPos
;
939 if (startPos
<= endPos
) {
940 endSearch
= endPos
- lengthFind
+ 1;
942 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
943 char firstChar
= s
[0];
945 firstChar
= static_cast<char>(MakeUpperCase(firstChar
));
947 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
948 char ch
= CharAt(pos
);
950 if (ch
== firstChar
) {
952 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
953 ch
= CharAt(pos
+ posMatch
);
954 if (ch
!= s
[posMatch
])
958 if ((!word
&& !wordStart
) ||
959 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
960 wordStart
&& IsWordStartAt(pos
))
965 if (MakeUpperCase(ch
) == firstChar
) {
967 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
968 ch
= CharAt(pos
+ posMatch
);
969 if (MakeUpperCase(ch
) != MakeUpperCase(s
[posMatch
]))
973 if ((!word
&& !wordStart
) ||
974 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
975 wordStart
&& IsWordStartAt(pos
))
982 // Ensure trying to match from start of character
983 pos
= MovePositionOutsideChar(pos
, increment
, false);
987 //Platform::DebugPrintf("Not found\n");
991 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
994 delete []substituted
;
996 DocumentIndexer
di(this, Length());
997 if (!pre
->GrabMatches(di
))
999 unsigned int lenResult
= 0;
1000 for (int i
= 0; i
< *length
; i
++) {
1001 if ((text
[i
] == '\\') && (text
[i
+ 1] >= '1' && text
[i
+ 1] <= '9')) {
1002 unsigned int patNum
= text
[i
+ 1] - '0';
1003 lenResult
+= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1009 substituted
= new char[lenResult
+ 1];
1012 char *o
= substituted
;
1013 for (int j
= 0; j
< *length
; j
++) {
1014 if ((text
[j
] == '\\') && (text
[j
+ 1] >= '1' && text
[j
+ 1] <= '9')) {
1015 unsigned int patNum
= text
[j
+ 1] - '0';
1016 unsigned int len
= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1017 if (pre
->pat
[patNum
]) // Will be null if try for a match that did not occur
1018 memcpy(o
, pre
->pat
[patNum
], len
);
1026 *length
= lenResult
;
1030 int Document::LinesTotal() {
1034 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
1035 for (int pos
= r
.start
; pos
< r
.end
; pos
++) {
1036 char ch
= CharAt(pos
);
1037 if (dbcsCodePage
&& IsDBCS(pos
)) {
1038 pos
+= LenChar(pos
);
1040 if (makeUpperCase
) {
1042 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1046 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1053 void Document::SetWordChars(unsigned char *chars
) {
1055 for (ch
= 0; ch
< 256; ch
++) {
1056 if (ch
== '\r' || ch
== '\n')
1057 charClass
[ch
] = ccNewLine
;
1058 else if (ch
< 0x20 || ch
== ' ')
1059 charClass
[ch
] = ccSpace
;
1061 charClass
[ch
] = ccPunctuation
;
1065 charClass
[*chars
] = ccWord
;
1069 for (ch
= 0; ch
< 256; ch
++) {
1070 if (ch
>= 0x80 || isalnum(ch
) || ch
== '_')
1071 charClass
[ch
] = ccWord
;
1076 void Document::SetStylingBits(int bits
) {
1078 stylingBitsMask
= 0;
1079 for (int bit
= 0; bit
< stylingBits
; bit
++) {
1080 stylingBitsMask
<<= 1;
1081 stylingBitsMask
|= 1;
1085 void Document::StartStyling(int position
, char mask
) {
1087 endStyled
= position
;
1090 bool Document::SetStyleFor(int length
, char style
) {
1091 if (enteredCount
!= 0) {
1095 int prevEndStyled
= endStyled
;
1096 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1097 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1098 prevEndStyled
, length
);
1101 endStyled
+= length
;
1107 bool Document::SetStyles(int length
, char *styles
) {
1108 if (enteredCount
!= 0) {
1112 int prevEndStyled
= endStyled
;
1113 bool didChange
= false;
1115 for (int iPos
= 0; iPos
< length
; iPos
++, endStyled
++) {
1116 PLATFORM_ASSERT(endStyled
< Length());
1117 if (cb
.SetStyleAt(endStyled
, styles
[iPos
], stylingMask
)) {
1123 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1124 prevEndStyled
, lastChange
);
1132 bool Document::EnsureStyledTo(int pos
) {
1133 if (pos
> GetEndStyled()) {
1135 if (styleClock
> 0x100000) {
1138 // Ask the watchers to style, and stop as soon as one responds.
1139 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++) {
1140 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
1143 return pos
<= GetEndStyled();
1146 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
1147 for (int i
= 0; i
< lenWatchers
; i
++) {
1148 if ((watchers
[i
].watcher
== watcher
) &&
1149 (watchers
[i
].userData
== userData
))
1152 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1155 for (int j
= 0; j
< lenWatchers
; j
++)
1156 pwNew
[j
] = watchers
[j
];
1157 pwNew
[lenWatchers
].watcher
= watcher
;
1158 pwNew
[lenWatchers
].userData
= userData
;
1165 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
1166 for (int i
= 0; i
< lenWatchers
; i
++) {
1167 if ((watchers
[i
].watcher
== watcher
) &&
1168 (watchers
[i
].userData
== userData
)) {
1169 if (lenWatchers
== 1) {
1174 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1177 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1178 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1190 void Document::NotifyModifyAttempt() {
1191 for (int i
= 0; i
< lenWatchers
; i
++) {
1192 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1196 void Document::NotifySavePoint(bool atSavePoint
) {
1197 for (int i
= 0; i
< lenWatchers
; i
++) {
1198 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1202 void Document::NotifyModified(DocModification mh
) {
1203 for (int i
= 0; i
< lenWatchers
; i
++) {
1204 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1208 bool Document::IsWordPartSeparator(char ch
) {
1209 return ispunct(ch
) && (WordCharClass(ch
) == ccWord
);
1212 int Document::WordPartLeft(int pos
) {
1215 char startChar
= cb
.CharAt(pos
);
1216 if (IsWordPartSeparator(startChar
)) {
1217 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1222 startChar
= cb
.CharAt(pos
);
1224 if (islower(startChar
)) {
1225 while (pos
> 0 && islower(cb
.CharAt(pos
)))
1227 if (!isupper(cb
.CharAt(pos
)) && !islower(cb
.CharAt(pos
)))
1229 } else if (isupper(startChar
)) {
1230 while (pos
> 0 && isupper(cb
.CharAt(pos
)))
1232 if (!isupper(cb
.CharAt(pos
)))
1234 } else if (isdigit(startChar
)) {
1235 while (pos
> 0 && isdigit(cb
.CharAt(pos
)))
1237 if (!isdigit(cb
.CharAt(pos
)))
1239 } else if (ispunct(startChar
)) {
1240 while (pos
> 0 && ispunct(cb
.CharAt(pos
)))
1242 if (!ispunct(cb
.CharAt(pos
)))
1244 } else if (isspacechar(startChar
)) {
1245 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1247 if (!isspacechar(cb
.CharAt(pos
)))
1255 int Document::WordPartRight(int pos
) {
1256 char startChar
= cb
.CharAt(pos
);
1257 int length
= Length();
1258 if (IsWordPartSeparator(startChar
)) {
1259 while (pos
< length
&& IsWordPartSeparator(cb
.CharAt(pos
)))
1261 startChar
= cb
.CharAt(pos
);
1263 if (islower(startChar
)) {
1264 while (pos
< length
&& islower(cb
.CharAt(pos
)))
1266 } else if (isupper(startChar
)) {
1267 if (islower(cb
.CharAt(pos
+ 1))) {
1269 while (pos
< length
&& islower(cb
.CharAt(pos
)))
1272 while (pos
< length
&& isupper(cb
.CharAt(pos
)))
1275 if (islower(cb
.CharAt(pos
)) && isupper(cb
.CharAt(pos
- 1)))
1277 } else if (isdigit(startChar
)) {
1278 while (pos
< length
&& isdigit(cb
.CharAt(pos
)))
1280 } else if (ispunct(startChar
)) {
1281 while (pos
< length
&& ispunct(cb
.CharAt(pos
)))
1283 } else if (isspacechar(startChar
)) {
1284 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))