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;
53 SetDefaultCharClasses(true);
57 enteredReadOnlyCount
= 0;
60 actualIndentInChars
= 8;
63 backspaceUnindents
= false;
72 Document::~Document() {
73 for (int i
= 0; i
< lenWatchers
; i
++) {
74 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
85 // Increase reference count and return its previous value.
86 int Document::AddRef() {
90 // Decrease reference count and return its previous value.
91 // Delete the document if reference count reaches zero.
92 int Document::Release() {
93 int curRefCount
= --refCount
;
99 void Document::SetSavePoint() {
101 NotifySavePoint(true);
104 int Document::AddMark(int line
, int markerNum
) {
105 int prev
= cb
.AddMark(line
, markerNum
);
106 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
112 void Document::AddMarkSet(int line
, int valueSet
) {
113 unsigned int m
= valueSet
;
114 for (int i
= 0; m
; i
++, m
>>= 1)
117 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
122 void Document::DeleteMark(int line
, int markerNum
) {
123 cb
.DeleteMark(line
, markerNum
);
124 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
129 void Document::DeleteMarkFromHandle(int markerHandle
) {
130 cb
.DeleteMarkFromHandle(markerHandle
);
131 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
136 void Document::DeleteAllMarks(int markerNum
) {
137 cb
.DeleteAllMarks(markerNum
);
138 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
143 int Document::LineStart(int line
) {
144 return cb
.LineStart(line
);
147 int Document::LineEnd(int line
) {
148 if (line
== LinesTotal() - 1) {
149 return LineStart(line
+ 1);
151 int position
= LineStart(line
+ 1) - 1;
152 // When line terminator is CR+LF, may need to go back one more
153 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
160 int Document::LineFromPosition(int pos
) {
161 return cb
.LineFromPosition(pos
);
164 int Document::LineEndPosition(int position
) {
165 return LineEnd(LineFromPosition(position
));
168 int Document::VCHomePosition(int position
) {
169 int line
= LineFromPosition(position
);
170 int startPosition
= LineStart(line
);
171 int endLine
= LineStart(line
+ 1) - 1;
172 int startText
= startPosition
;
173 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t' ) )
175 if (position
== startText
)
176 return startPosition
;
181 int Document::SetLevel(int line
, int level
) {
182 int prev
= cb
.SetLevel(line
, level
);
184 DocModification
mh(SC_MOD_CHANGEFOLD
| SC_MOD_CHANGEMARKER
,
185 LineStart(line
), 0, 0, 0);
187 mh
.foldLevelNow
= level
;
188 mh
.foldLevelPrev
= prev
;
194 static bool IsSubordinate(int levelStart
, int levelTry
) {
195 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
198 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
201 int Document::GetLastChild(int lineParent
, int level
) {
203 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
204 int maxLine
= LinesTotal();
205 int lineMaxSubord
= lineParent
;
206 while (lineMaxSubord
< maxLine
- 1) {
207 EnsureStyledTo(LineStart(lineMaxSubord
+ 2));
208 if (!IsSubordinate(level
, GetLevel(lineMaxSubord
+ 1)))
212 if (lineMaxSubord
> lineParent
) {
213 if (level
> (GetLevel(lineMaxSubord
+ 1) & SC_FOLDLEVELNUMBERMASK
)) {
214 // Have chewed up some whitespace that belongs to a parent so seek back
215 if (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
) {
220 return lineMaxSubord
;
223 int Document::GetFoldParent(int line
) {
224 int level
= GetLevel(line
) & SC_FOLDLEVELNUMBERMASK
;
225 int lineLook
= line
- 1;
226 while ((lineLook
> 0) && (
227 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
228 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
232 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
233 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
240 int Document::ClampPositionIntoDocument(int pos
) {
241 return Platform::Clamp(pos
, 0, Length());
244 bool Document::IsCrLf(int pos
) {
247 if (pos
>= (Length() - 1))
249 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
252 static const int maxBytesInDBCSCharacter
=5;
254 int Document::LenChar(int pos
) {
257 } else if (IsCrLf(pos
)) {
259 } else if (SC_CP_UTF8
== dbcsCodePage
) {
260 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
264 if (ch
>= (0x80 + 0x40 + 0x20))
266 int lengthDoc
= Length();
267 if ((pos
+ len
) > lengthDoc
)
268 return lengthDoc
-pos
;
271 } else if (dbcsCodePage
) {
272 char mbstr
[maxBytesInDBCSCharacter
+1];
274 for (i
=0; i
<Platform::DBCSCharMaxLength(); i
++) {
275 mbstr
[i
] = cb
.CharAt(pos
+i
);
278 return Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
284 // Normalise a position so that it is not halfway through a two byte character.
285 // This can occur in two situations -
286 // When lines are terminated with \r\n pairs which should be treated as one character.
287 // When displaying DBCS text such as Japanese.
288 // If moving, move the position in the indicated direction.
289 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
290 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
291 // If out of range, just return minimum/maximum value.
297 // PLATFORM_ASSERT(pos > 0 && pos < Length());
298 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
305 // Not between CR and LF
308 if (SC_CP_UTF8
== dbcsCodePage
) {
309 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
310 while ((pos
> 0) && (pos
< Length()) && (ch
>= 0x80) && (ch
< (0x80 + 0x40))) {
311 // ch is a trail byte
316 ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
319 // Anchor DBCS calculations at start of line because start of line can
320 // not be a DBCS trail byte.
321 int posCheck
= LineStart(LineFromPosition(pos
));
322 while (posCheck
< pos
) {
323 char mbstr
[maxBytesInDBCSCharacter
+1];
325 for(i
=0;i
<Platform::DBCSCharMaxLength();i
++) {
326 mbstr
[i
] = cb
.CharAt(posCheck
+i
);
330 int mbsize
= Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
331 if (posCheck
+ mbsize
== pos
) {
333 } else if (posCheck
+ mbsize
> pos
) {
335 return posCheck
+ mbsize
;
348 void Document::ModifiedAt(int pos
) {
353 void Document::CheckReadOnly() {
354 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
355 enteredReadOnlyCount
++;
356 NotifyModifyAttempt();
357 enteredReadOnlyCount
--;
361 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
362 // SetStyleAt does not change the persistent state of a document
364 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
365 bool Document::DeleteChars(int pos
, int len
) {
368 if ((pos
+ len
) > Length())
371 if (enteredCount
!= 0) {
375 if (!cb
.IsReadOnly()) {
378 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
381 int prevLinesTotal
= LinesTotal();
382 bool startSavePoint
= cb
.IsSavePoint();
383 const char *text
= cb
.DeleteChars(pos
* 2, len
* 2);
384 if (startSavePoint
&& cb
.IsCollectingUndo())
385 NotifySavePoint(!startSavePoint
);
386 if ((pos
< Length()) || (pos
== 0))
392 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
,
394 LinesTotal() - prevLinesTotal
, text
));
398 return !cb
.IsReadOnly();
402 * Insert a styled string (char/style pairs) with a length.
404 bool Document::InsertStyledString(int position
, char *s
, int insertLength
) {
406 if (enteredCount
!= 0) {
410 if (!cb
.IsReadOnly()) {
413 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
414 position
/ 2, insertLength
/ 2,
416 int prevLinesTotal
= LinesTotal();
417 bool startSavePoint
= cb
.IsSavePoint();
418 const char *text
= cb
.InsertString(position
, s
, insertLength
);
419 if (startSavePoint
&& cb
.IsCollectingUndo())
420 NotifySavePoint(!startSavePoint
);
421 ModifiedAt(position
/ 2);
424 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
,
425 position
/ 2, insertLength
/ 2,
426 LinesTotal() - prevLinesTotal
, text
));
430 return !cb
.IsReadOnly();
433 int Document::Undo() {
436 if (enteredCount
== 0) {
438 if (!cb
.IsReadOnly()) {
439 bool startSavePoint
= cb
.IsSavePoint();
440 bool multiLine
= false;
441 int steps
= cb
.StartUndo();
442 //Platform::DebugPrintf("Steps=%d\n", steps);
443 for (int step
= 0; step
< steps
; step
++) {
444 const int prevLinesTotal
= LinesTotal();
445 const Action
&action
= cb
.GetUndoStep();
446 if (action
.at
== removeAction
) {
447 NotifyModified(DocModification(
448 SC_MOD_BEFOREINSERT
| SC_PERFORMED_UNDO
, action
));
450 NotifyModified(DocModification(
451 SC_MOD_BEFOREDELETE
| SC_PERFORMED_UNDO
, action
));
453 cb
.PerformUndoStep();
454 int cellPosition
= action
.position
;
455 ModifiedAt(cellPosition
);
456 newPos
= cellPosition
;
458 int modFlags
= SC_PERFORMED_UNDO
;
459 // With undo, an insertion action becomes a deletion notification
460 if (action
.at
== removeAction
) {
461 newPos
+= action
.lenData
;
462 modFlags
|= SC_MOD_INSERTTEXT
;
464 modFlags
|= SC_MOD_DELETETEXT
;
467 modFlags
|= SC_MULTISTEPUNDOREDO
;
468 const int linesAdded
= LinesTotal() - prevLinesTotal
;
471 if (step
== steps
- 1) {
472 modFlags
|= SC_LASTSTEPINUNDOREDO
;
474 modFlags
|= SC_MULTILINEUNDOREDO
;
476 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
477 linesAdded
, action
.data
));
480 bool endSavePoint
= cb
.IsSavePoint();
481 if (startSavePoint
!= endSavePoint
)
482 NotifySavePoint(endSavePoint
);
489 int Document::Redo() {
492 if (enteredCount
== 0) {
494 if (!cb
.IsReadOnly()) {
495 bool startSavePoint
= cb
.IsSavePoint();
496 bool multiLine
= false;
497 int steps
= cb
.StartRedo();
498 for (int step
= 0; step
< steps
; step
++) {
499 const int prevLinesTotal
= LinesTotal();
500 const Action
&action
= cb
.GetRedoStep();
501 if (action
.at
== insertAction
) {
502 NotifyModified(DocModification(
503 SC_MOD_BEFOREINSERT
| SC_PERFORMED_REDO
, action
));
505 NotifyModified(DocModification(
506 SC_MOD_BEFOREDELETE
| SC_PERFORMED_REDO
, action
));
508 cb
.PerformRedoStep();
509 ModifiedAt(action
.position
);
510 newPos
= action
.position
;
512 int modFlags
= SC_PERFORMED_REDO
;
513 if (action
.at
== insertAction
) {
514 newPos
+= action
.lenData
;
515 modFlags
|= SC_MOD_INSERTTEXT
;
517 modFlags
|= SC_MOD_DELETETEXT
;
520 modFlags
|= SC_MULTISTEPUNDOREDO
;
521 const int linesAdded
= LinesTotal() - prevLinesTotal
;
524 if (step
== steps
- 1) {
525 modFlags
|= SC_LASTSTEPINUNDOREDO
;
527 modFlags
|= SC_MULTILINEUNDOREDO
;
530 DocModification(modFlags
, action
.position
, action
.lenData
,
531 linesAdded
, action
.data
));
534 bool endSavePoint
= cb
.IsSavePoint();
535 if (startSavePoint
!= endSavePoint
)
536 NotifySavePoint(endSavePoint
);
544 * Insert a single character.
546 bool Document::InsertChar(int pos
, char ch
) {
550 return InsertStyledString(pos
*2, chs
, 2);
554 * Insert a null terminated string.
556 bool Document::InsertString(int position
, const char *s
) {
557 return InsertString(position
, s
, strlen(s
));
561 * Insert a string with a length.
563 bool Document::InsertString(int position
, const char *s
, size_t insertLength
) {
564 bool changed
= false;
565 if (insertLength
> 0) {
566 char *sWithStyle
= new char[insertLength
* 2];
568 for (size_t i
= 0; i
< insertLength
; i
++) {
569 sWithStyle
[i
*2] = s
[i
];
570 sWithStyle
[i
*2 + 1] = 0;
572 changed
= InsertStyledString(position
*2, sWithStyle
,
573 static_cast<int>(insertLength
*2));
580 void Document::ChangeChar(int pos
, char ch
) {
585 void Document::DelChar(int pos
) {
586 DeleteChars(pos
, LenChar(pos
));
589 void Document::DelCharBack(int pos
) {
592 } else if (IsCrLf(pos
- 2)) {
593 DeleteChars(pos
- 2, 2);
594 } else if (dbcsCodePage
) {
595 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
596 DeleteChars(startChar
, pos
- startChar
);
598 DeleteChars(pos
- 1, 1);
602 static bool isindentchar(char ch
) {
603 return (ch
== ' ') || (ch
== '\t');
606 static int NextTab(int pos
, int tabSize
) {
607 return ((pos
/ tabSize
) + 1) * tabSize
;
610 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
611 length
--; // ensure space for \0
613 while ((indent
>= tabSize
) && (length
> 0)) {
619 while ((indent
> 0) && (length
> 0)) {
627 int Document::GetLineIndentation(int line
) {
629 if ((line
>= 0) && (line
< LinesTotal())) {
630 int lineStart
= LineStart(line
);
631 int length
= Length();
632 for (int i
= lineStart
;i
< length
;i
++) {
633 char ch
= cb
.CharAt(i
);
637 indent
= NextTab(indent
, tabInChars
);
645 void Document::SetLineIndentation(int line
, int indent
) {
646 int indentOfLine
= GetLineIndentation(line
);
649 if (indent
!= indentOfLine
) {
651 CreateIndentation(linebuf
, sizeof(linebuf
), indent
, tabInChars
, !useTabs
);
652 int thisLineStart
= LineStart(line
);
653 int indentPos
= GetLineIndentPosition(line
);
655 DeleteChars(thisLineStart
, indentPos
- thisLineStart
);
656 InsertString(thisLineStart
, linebuf
);
661 int Document::GetLineIndentPosition(int line
) {
664 int pos
= LineStart(line
);
665 int length
= Length();
666 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
672 int Document::GetColumn(int pos
) {
674 int line
= LineFromPosition(pos
);
675 if ((line
>= 0) && (line
< LinesTotal())) {
676 for (int i
= LineStart(line
);i
< pos
;) {
677 char ch
= cb
.CharAt(i
);
679 column
= NextTab(column
, tabInChars
);
681 } else if (ch
== '\r') {
683 } else if (ch
== '\n') {
687 i
= MovePositionOutsideChar(i
+ 1, 1);
694 int Document::FindColumn(int line
, int column
) {
695 int position
= LineStart(line
);
696 int columnCurrent
= 0;
697 if ((line
>= 0) && (line
< LinesTotal())) {
698 while ((columnCurrent
< column
) && (position
< Length())) {
699 char ch
= cb
.CharAt(position
);
701 columnCurrent
= NextTab(columnCurrent
, tabInChars
);
703 } else if (ch
== '\r') {
705 } else if (ch
== '\n') {
709 position
= MovePositionOutsideChar(position
+ 1, 1);
716 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
717 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
718 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
719 int indentOfLine
= GetLineIndentation(line
);
721 if (LineStart(line
) < LineEnd(line
)) {
722 SetLineIndentation(line
, indentOfLine
+ IndentSize());
725 SetLineIndentation(line
, indentOfLine
- IndentSize());
730 // Convert line endings for a piece of text to a particular mode.
731 // Stop at len or when a NUL is found.
732 // Caller must delete the returned pointer.
733 char *Document::TransformLineEnds(int *pLenOut
, const char *s
, size_t len
, int eolMode
) {
734 char *dest
= new char[2 * len
+ 1];
735 const char *sptr
= s
;
737 for (size_t i
= 0; (i
< len
) && (*sptr
!= '\0'); i
++) {
738 if (*sptr
== '\n' || *sptr
== '\r') {
739 if (eolMode
== SC_EOL_CR
) {
741 } else if (eolMode
== SC_EOL_LF
) {
743 } else { // eolMode == SC_EOL_CRLF
747 if ((*sptr
== '\r') && (i
+1 < len
) && (*(sptr
+1) == '\n')) {
757 *pLenOut
= (dptr
- dest
) - 1;
761 void Document::ConvertLineEnds(int eolModeSet
) {
764 for (int pos
= 0; pos
< Length(); pos
++) {
765 if (cb
.CharAt(pos
) == '\r') {
766 if (cb
.CharAt(pos
+ 1) == '\n') {
768 if (eolModeSet
== SC_EOL_CR
) {
769 DeleteChars(pos
+ 1, 1); // Delete the LF
770 } else if (eolModeSet
== SC_EOL_LF
) {
771 DeleteChars(pos
, 1); // Delete the CR
777 if (eolModeSet
== SC_EOL_CRLF
) {
778 InsertString(pos
+ 1, "\n", 1); // Insert LF
780 } else if (eolModeSet
== SC_EOL_LF
) {
781 InsertString(pos
, "\n", 1); // Insert LF
782 DeleteChars(pos
+ 1, 1); // Delete CR
785 } else if (cb
.CharAt(pos
) == '\n') {
787 if (eolModeSet
== SC_EOL_CRLF
) {
788 InsertString(pos
, "\r", 1); // Insert CR
790 } else if (eolModeSet
== SC_EOL_CR
) {
791 InsertString(pos
, "\r", 1); // Insert CR
792 DeleteChars(pos
+ 1, 1); // Delete LF
800 bool Document::IsWhiteLine(int line
) {
801 int currentChar
= LineStart(line
);
802 int endLine
= LineEnd(line
);
803 while (currentChar
< endLine
) {
804 if (cb
.CharAt(currentChar
) != ' ' && cb
.CharAt(currentChar
) != '\t') {
812 int Document::ParaUp(int pos
) {
813 int line
= LineFromPosition(pos
);
815 while (line
>= 0 && IsWhiteLine(line
)) { // skip empty lines
818 while (line
>= 0 && !IsWhiteLine(line
)) { // skip non-empty lines
822 return LineStart(line
);
825 int Document::ParaDown(int pos
) {
826 int line
= LineFromPosition(pos
);
827 while (line
< LinesTotal() && !IsWhiteLine(line
)) { // skip non-empty lines
830 while (line
< LinesTotal() && IsWhiteLine(line
)) { // skip empty lines
833 if (line
< LinesTotal())
834 return LineStart(line
);
835 else // end of a document
836 return LineEnd(line
-1);
839 Document::charClassification
Document::WordCharClass(unsigned char ch
) {
840 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
>= 0x80))
842 return charClass
[ch
];
846 * Used by commmands that want to select whole words.
847 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
849 int Document::ExtendWordSelect(int pos
, int delta
, bool onlyWordCharacters
) {
850 charClassification ccStart
= ccWord
;
852 if (!onlyWordCharacters
)
853 ccStart
= WordCharClass(cb
.CharAt(pos
-1));
854 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
))
857 if (!onlyWordCharacters
)
858 ccStart
= WordCharClass(cb
.CharAt(pos
));
859 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
862 return MovePositionOutsideChar(pos
, delta
);
866 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
868 * This is looking for a transition between character classes although there is also some
869 * additional movement to transit white space.
870 * Used by cursor movement by word commands.
872 int Document::NextWordStart(int pos
, int delta
) {
874 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccSpace
))
877 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
-1));
878 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
)) {
883 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
));
884 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
886 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccSpace
))
893 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
895 * This is looking for a transition between character classes although there is also some
896 * additional movement to transit white space.
897 * Used by cursor movement by word commands.
899 int Document::NextWordEnd(int pos
, int delta
) {
902 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
-1));
903 if (ccStart
!= ccSpace
) {
904 while (pos
> 0 && WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
) {
908 while (pos
> 0 && WordCharClass(cb
.CharAt(pos
- 1)) == ccSpace
) {
913 while (pos
< Length() && WordCharClass(cb
.CharAt(pos
)) == ccSpace
) {
916 if (pos
< Length()) {
917 charClassification ccStart
= WordCharClass(cb
.CharAt(pos
));
918 while (pos
< Length() && WordCharClass(cb
.CharAt(pos
)) == ccStart
) {
927 * Check that the character at the given position is a word or punctuation character and that
928 * the previous character is of a different character class.
930 bool Document::IsWordStartAt(int pos
) {
932 charClassification ccPos
= WordCharClass(CharAt(pos
));
933 return (ccPos
== ccWord
|| ccPos
== ccPunctuation
) &&
934 (ccPos
!= WordCharClass(CharAt(pos
- 1)));
940 * Check that the character at the given position is a word or punctuation character and that
941 * the next character is of a different character class.
943 bool Document::IsWordEndAt(int pos
) {
944 if (pos
< Length()) {
945 charClassification ccPrev
= WordCharClass(CharAt(pos
-1));
946 return (ccPrev
== ccWord
|| ccPrev
== ccPunctuation
) &&
947 (ccPrev
!= WordCharClass(CharAt(pos
)));
953 * Check that the given range is has transitions between character classes at both
954 * ends and where the characters on the inside are word or punctuation characters.
956 bool Document::IsWordAt(int start
, int end
) {
957 return IsWordStartAt(start
) && IsWordEndAt(end
);
960 // The comparison and case changing functions here assume ASCII
961 // or extended ASCII such as the normal Windows code page.
963 static inline char MakeUpperCase(char ch
) {
964 if (ch
< 'a' || ch
> 'z')
967 return static_cast<char>(ch
- 'a' + 'A');
970 static inline char MakeLowerCase(char ch
) {
971 if (ch
< 'A' || ch
> 'Z')
974 return static_cast<char>(ch
- 'A' + 'a');
977 // Define a way for the Regular Expression code to access the document
978 class DocumentIndexer
: public CharacterIndexer
{
982 DocumentIndexer(Document
*pdoc_
, int end_
) :
983 pdoc(pdoc_
), end(end_
) {
986 virtual ~DocumentIndexer() {
989 virtual char CharAt(int index
) {
990 if (index
< 0 || index
>= end
)
993 return pdoc
->CharAt(index
);
998 * Find text in document, supporting both forward and backward
999 * searches (just pass minPos > maxPos to do a backward search)
1000 * Has not been tested with backwards DBCS searches yet.
1002 long Document::FindText(int minPos
, int maxPos
, const char *s
,
1003 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
, bool posix
,
1007 pre
= new RESearch();
1011 int increment
= (minPos
<= maxPos
) ? 1 : -1;
1013 int startPos
= minPos
;
1014 int endPos
= maxPos
;
1016 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1017 startPos
= MovePositionOutsideChar(startPos
, 1, false);
1018 endPos
= MovePositionOutsideChar(endPos
, 1, false);
1020 const char *errmsg
= pre
->Compile(s
, *length
, caseSensitive
, posix
);
1024 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
1025 // Replace first '.' with '-' in each property file variable reference:
1026 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
1027 // Replace: $(\1-\2)
1028 int lineRangeStart
= LineFromPosition(startPos
);
1029 int lineRangeEnd
= LineFromPosition(endPos
);
1030 if ((increment
== 1) &&
1031 (startPos
>= LineEnd(lineRangeStart
)) &&
1032 (lineRangeStart
< lineRangeEnd
)) {
1033 // the start position is at end of line or between line end characters.
1035 startPos
= LineStart(lineRangeStart
);
1039 char searchEnd
= s
[*length
- 1];
1040 int lineRangeBreak
= lineRangeEnd
+ increment
;
1041 for (int line
= lineRangeStart
; line
!= lineRangeBreak
; line
+= increment
) {
1042 int startOfLine
= LineStart(line
);
1043 int endOfLine
= LineEnd(line
);
1044 if (increment
== 1) {
1045 if (line
== lineRangeStart
) {
1046 if ((startPos
!= startOfLine
) && (s
[0] == '^'))
1047 continue; // Can't match start of line if start position after start of line
1048 startOfLine
= startPos
;
1050 if (line
== lineRangeEnd
) {
1051 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
1052 continue; // Can't match end of line if end position before end of line
1056 if (line
== lineRangeEnd
) {
1057 if ((endPos
!= startOfLine
) && (s
[0] == '^'))
1058 continue; // Can't match start of line if end position after start of line
1059 startOfLine
= endPos
;
1061 if (line
== lineRangeStart
) {
1062 if ((startPos
!= endOfLine
) && (searchEnd
== '$'))
1063 continue; // Can't match end of line if start position before end of line
1064 endOfLine
= startPos
;
1068 DocumentIndexer
di(this, endOfLine
);
1069 int success
= pre
->Execute(di
, startOfLine
, endOfLine
);
1071 pos
= pre
->bopat
[0];
1072 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
1073 if (increment
== -1) {
1074 // Check for the last match on this line.
1075 int repetitions
= 1000; // Break out of infinite loop
1076 while (success
&& (pre
->eopat
[0] <= endOfLine
) && (repetitions
--)) {
1077 success
= pre
->Execute(di
, pos
+1, endOfLine
);
1079 if (pre
->eopat
[0] <= minPos
) {
1080 pos
= pre
->bopat
[0];
1081 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
1096 bool forward
= minPos
<= maxPos
;
1097 int increment
= forward
? 1 : -1;
1099 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1100 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
1101 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
1103 // Compute actual search ranges needed
1104 int lengthFind
= *length
;
1105 if (lengthFind
== -1)
1106 lengthFind
= static_cast<int>(strlen(s
));
1107 int endSearch
= endPos
;
1108 if (startPos
<= endPos
) {
1109 endSearch
= endPos
- lengthFind
+ 1;
1111 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
1112 char firstChar
= s
[0];
1114 firstChar
= static_cast<char>(MakeUpperCase(firstChar
));
1115 int pos
= forward
? startPos
: (startPos
- 1);
1116 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
1117 char ch
= CharAt(pos
);
1118 if (caseSensitive
) {
1119 if (ch
== firstChar
) {
1121 if (pos
+ lengthFind
> Platform::Maximum(startPos
, endPos
)) found
= false;
1122 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
1123 ch
= CharAt(pos
+ posMatch
);
1124 if (ch
!= s
[posMatch
])
1128 if ((!word
&& !wordStart
) ||
1129 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
1130 wordStart
&& IsWordStartAt(pos
))
1135 if (MakeUpperCase(ch
) == firstChar
) {
1137 if (pos
+ lengthFind
> Platform::Maximum(startPos
, endPos
)) found
= false;
1138 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
1139 ch
= CharAt(pos
+ posMatch
);
1140 if (MakeUpperCase(ch
) != MakeUpperCase(s
[posMatch
]))
1144 if ((!word
&& !wordStart
) ||
1145 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
1146 wordStart
&& IsWordStartAt(pos
))
1152 if (dbcsCodePage
&& (pos
>= 0)) {
1153 // Ensure trying to match from start of character
1154 pos
= MovePositionOutsideChar(pos
, increment
, false);
1158 //Platform::DebugPrintf("Not found\n");
1162 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
1165 delete []substituted
;
1167 DocumentIndexer
di(this, Length());
1168 if (!pre
->GrabMatches(di
))
1170 unsigned int lenResult
= 0;
1171 for (int i
= 0; i
< *length
; i
++) {
1172 if (text
[i
] == '\\') {
1173 if (text
[i
+ 1] >= '1' && text
[i
+ 1] <= '9') {
1174 unsigned int patNum
= text
[i
+ 1] - '0';
1175 lenResult
+= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1178 switch (text
[i
+ 1]) {
1194 substituted
= new char[lenResult
+ 1];
1197 char *o
= substituted
;
1198 for (int j
= 0; j
< *length
; j
++) {
1199 if (text
[j
] == '\\') {
1200 if (text
[j
+ 1] >= '1' && text
[j
+ 1] <= '9') {
1201 unsigned int patNum
= text
[j
+ 1] - '0';
1202 unsigned int len
= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1203 if (pre
->pat
[patNum
]) // Will be null if try for a match that did not occur
1204 memcpy(o
, pre
->pat
[patNum
], len
);
1241 *length
= lenResult
;
1245 int Document::LinesTotal() {
1249 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
1250 for (int pos
= r
.start
; pos
< r
.end
;) {
1251 int len
= LenChar(pos
);
1253 char ch
= CharAt(pos
);
1254 if (makeUpperCase
) {
1255 if (IsLowerCase(ch
)) {
1256 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1259 if (IsUpperCase(ch
)) {
1260 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1268 void Document::SetDefaultCharClasses(bool includeWordClass
) {
1269 // Initialize all char classes to default values
1270 for (int ch
= 0; ch
< 256; ch
++) {
1271 if (ch
== '\r' || ch
== '\n')
1272 charClass
[ch
] = ccNewLine
;
1273 else if (ch
< 0x20 || ch
== ' ')
1274 charClass
[ch
] = ccSpace
;
1275 else if (includeWordClass
&& (ch
>= 0x80 || isalnum(ch
) || ch
== '_'))
1276 charClass
[ch
] = ccWord
;
1278 charClass
[ch
] = ccPunctuation
;
1282 void Document::SetCharClasses(const unsigned char *chars
, charClassification newCharClass
) {
1283 // Apply the newCharClass to the specifed chars
1286 charClass
[*chars
] = newCharClass
;
1292 void Document::SetStylingBits(int bits
) {
1294 stylingBitsMask
= 0;
1295 for (int bit
= 0; bit
< stylingBits
; bit
++) {
1296 stylingBitsMask
<<= 1;
1297 stylingBitsMask
|= 1;
1301 void Document::StartStyling(int position
, char mask
) {
1303 endStyled
= position
;
1306 bool Document::SetStyleFor(int length
, char style
) {
1307 if (enteredCount
!= 0) {
1311 style
&= stylingMask
;
1312 int prevEndStyled
= endStyled
;
1313 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1314 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1315 prevEndStyled
, length
);
1318 endStyled
+= length
;
1324 bool Document::SetStyles(int length
, char *styles
) {
1325 if (enteredCount
!= 0) {
1329 bool didChange
= false;
1332 for (int iPos
= 0; iPos
< length
; iPos
++, endStyled
++) {
1333 PLATFORM_ASSERT(endStyled
< Length());
1334 if (cb
.SetStyleAt(endStyled
, styles
[iPos
], stylingMask
)) {
1336 startMod
= endStyled
;
1343 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1344 startMod
, endMod
- startMod
+ 1);
1352 bool Document::EnsureStyledTo(int pos
) {
1353 if (pos
> GetEndStyled()) {
1354 IncrementStyleClock();
1355 // Ask the watchers to style, and stop as soon as one responds.
1356 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++) {
1357 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
1360 return pos
<= GetEndStyled();
1363 void Document::IncrementStyleClock() {
1365 if (styleClock
> 0x100000) {
1370 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
1371 for (int i
= 0; i
< lenWatchers
; i
++) {
1372 if ((watchers
[i
].watcher
== watcher
) &&
1373 (watchers
[i
].userData
== userData
))
1376 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1379 for (int j
= 0; j
< lenWatchers
; j
++)
1380 pwNew
[j
] = watchers
[j
];
1381 pwNew
[lenWatchers
].watcher
= watcher
;
1382 pwNew
[lenWatchers
].userData
= userData
;
1389 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
1390 for (int i
= 0; i
< lenWatchers
; i
++) {
1391 if ((watchers
[i
].watcher
== watcher
) &&
1392 (watchers
[i
].userData
== userData
)) {
1393 if (lenWatchers
== 1) {
1398 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1401 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1402 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1414 void Document::NotifyModifyAttempt() {
1415 for (int i
= 0; i
< lenWatchers
; i
++) {
1416 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1420 void Document::NotifySavePoint(bool atSavePoint
) {
1421 for (int i
= 0; i
< lenWatchers
; i
++) {
1422 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1426 void Document::NotifyModified(DocModification mh
) {
1427 for (int i
= 0; i
< lenWatchers
; i
++) {
1428 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1432 bool Document::IsWordPartSeparator(char ch
) {
1433 return (WordCharClass(ch
) == ccWord
) && IsPunctuation(ch
);
1436 int Document::WordPartLeft(int pos
) {
1439 char startChar
= cb
.CharAt(pos
);
1440 if (IsWordPartSeparator(startChar
)) {
1441 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1446 startChar
= cb
.CharAt(pos
);
1448 if (IsLowerCase(startChar
)) {
1449 while (pos
> 0 && IsLowerCase(cb
.CharAt(pos
)))
1451 if (!IsUpperCase(cb
.CharAt(pos
)) && !IsLowerCase(cb
.CharAt(pos
)))
1453 } else if (IsUpperCase(startChar
)) {
1454 while (pos
> 0 && IsUpperCase(cb
.CharAt(pos
)))
1456 if (!IsUpperCase(cb
.CharAt(pos
)))
1458 } else if (IsADigit(startChar
)) {
1459 while (pos
> 0 && IsADigit(cb
.CharAt(pos
)))
1461 if (!IsADigit(cb
.CharAt(pos
)))
1463 } else if (IsPunctuation(startChar
)) {
1464 while (pos
> 0 && IsPunctuation(cb
.CharAt(pos
)))
1466 if (!IsPunctuation(cb
.CharAt(pos
)))
1468 } else if (isspacechar(startChar
)) {
1469 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1471 if (!isspacechar(cb
.CharAt(pos
)))
1473 } else if (!isascii(startChar
)) {
1474 while (pos
> 0 && !isascii(cb
.CharAt(pos
)))
1476 if (isascii(cb
.CharAt(pos
)))
1486 int Document::WordPartRight(int pos
) {
1487 char startChar
= cb
.CharAt(pos
);
1488 int length
= Length();
1489 if (IsWordPartSeparator(startChar
)) {
1490 while (pos
< length
&& IsWordPartSeparator(cb
.CharAt(pos
)))
1492 startChar
= cb
.CharAt(pos
);
1494 if (!isascii(startChar
)) {
1495 while (pos
< length
&& !isascii(cb
.CharAt(pos
)))
1497 } else if (IsLowerCase(startChar
)) {
1498 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1500 } else if (IsUpperCase(startChar
)) {
1501 if (IsLowerCase(cb
.CharAt(pos
+ 1))) {
1503 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1506 while (pos
< length
&& IsUpperCase(cb
.CharAt(pos
)))
1509 if (IsLowerCase(cb
.CharAt(pos
)) && IsUpperCase(cb
.CharAt(pos
- 1)))
1511 } else if (IsADigit(startChar
)) {
1512 while (pos
< length
&& IsADigit(cb
.CharAt(pos
)))
1514 } else if (IsPunctuation(startChar
)) {
1515 while (pos
< length
&& IsPunctuation(cb
.CharAt(pos
)))
1517 } else if (isspacechar(startChar
)) {
1518 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))
1526 bool IsLineEndChar(char c
) {
1527 return (c
== '\n' || c
== '\r');
1530 int Document::ExtendStyleRange(int pos
, int delta
, bool singleLine
) {
1531 int sStart
= cb
.StyleAt(pos
);
1533 while (pos
> 0 && (cb
.StyleAt(pos
) == sStart
) && (!singleLine
|| !IsLineEndChar(cb
.CharAt(pos
))) )
1537 while (pos
< (Length()) && (cb
.StyleAt(pos
) == sStart
) && (!singleLine
|| !IsLineEndChar(cb
.CharAt(pos
))) )
1543 static char BraceOpposite(char ch
) {
1566 // TODO: should be able to extend styled region to find matching brace
1567 int Document::BraceMatch(int position
, int /*maxReStyle*/) {
1568 char chBrace
= CharAt(position
);
1569 char chSeek
= BraceOpposite(chBrace
);
1572 char styBrace
= static_cast<char>(StyleAt(position
) & stylingBitsMask
);
1574 if (chBrace
== '(' || chBrace
== '[' || chBrace
== '{' || chBrace
== '<')
1577 position
= position
+ direction
;
1578 while ((position
>= 0) && (position
< Length())) {
1579 position
= MovePositionOutsideChar(position
, direction
);
1580 char chAtPos
= CharAt(position
);
1581 char styAtPos
= static_cast<char>(StyleAt(position
) & stylingBitsMask
);
1582 if ((position
> GetEndStyled()) || (styAtPos
== styBrace
)) {
1583 if (chAtPos
== chBrace
)
1585 if (chAtPos
== chSeek
)
1590 position
= position
+ direction
;