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.
16 // With Borland C++ 5.5, including <string> includes Windows.h leading to defining
17 // FindText to FindTextA which makes calls here to Document::FindText fail.
26 #include "Scintilla.h"
27 #include "SplitVector.h"
28 #include "Partitioning.h"
29 #include "RunStyles.h"
30 #include "CellBuffer.h"
32 #include "CharClassify.h"
33 #include "Decoration.h"
36 #include "UniConversion.h"
39 using namespace Scintilla
;
42 // This is ASCII specific but is safe with chars >= 0x80
43 static inline bool isspacechar(unsigned char ch
) {
44 return (ch
== ' ') || ((ch
>= 0x09) && (ch
<= 0x0d));
47 static inline bool IsPunctuation(char ch
) {
48 return isascii(ch
) && ispunct(ch
);
51 static inline bool IsADigit(char ch
) {
52 return isascii(ch
) && isdigit(ch
);
55 static inline bool IsLowerCase(char ch
) {
56 return isascii(ch
) && islower(ch
);
59 static inline bool IsUpperCase(char ch
) {
60 return isascii(ch
) && isupper(ch
);
63 Document::Document() {
68 eolMode
= SC_EOL_CRLF
;
72 stylingBitsMask
= 0x1F;
76 enteredModification
= 0;
78 enteredReadOnlyCount
= 0;
81 actualIndentInChars
= 8;
84 backspaceUnindents
= false;
91 perLineData
[ldMarkers
] = new LineMarkers();
92 perLineData
[ldLevels
] = new LineLevels();
93 perLineData
[ldState
] = new LineState();
94 perLineData
[ldMargin
] = new LineAnnotation();
95 perLineData
[ldAnnotation
] = new LineAnnotation();
100 Document::~Document() {
101 for (int i
= 0; i
< lenWatchers
; i
++) {
102 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
105 for (int j
=0; j
<ldSize
; j
++) {
106 delete perLineData
[j
];
115 void Document::Init() {
116 for (int j
=0; j
<ldSize
; j
++) {
118 perLineData
[j
]->Init();
122 void Document::InsertLine(int line
) {
123 for (int j
=0; j
<ldSize
; j
++) {
125 perLineData
[j
]->InsertLine(line
);
129 void Document::RemoveLine(int line
) {
130 for (int j
=0; j
<ldSize
; j
++) {
132 perLineData
[j
]->RemoveLine(line
);
136 // Increase reference count and return its previous value.
137 int Document::AddRef() {
141 // Decrease reference count and return its previous value.
142 // Delete the document if reference count reaches zero.
143 int Document::Release() {
144 int curRefCount
= --refCount
;
145 if (curRefCount
== 0)
150 void Document::SetSavePoint() {
152 NotifySavePoint(true);
155 int Document::GetMark(int line
) {
156 return static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->MarkValue(line
);
159 int Document::AddMark(int line
, int markerNum
) {
160 if (line
<= LinesTotal()) {
161 int prev
= static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->
162 AddMark(line
, markerNum
, LinesTotal());
163 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
171 void Document::AddMarkSet(int line
, int valueSet
) {
172 unsigned int m
= valueSet
;
173 for (int i
= 0; m
; i
++, m
>>= 1)
175 static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->
176 AddMark(line
, i
, LinesTotal());
177 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
181 void Document::DeleteMark(int line
, int markerNum
) {
182 static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->DeleteMark(line
, markerNum
, false);
183 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
187 void Document::DeleteMarkFromHandle(int markerHandle
) {
188 static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->DeleteMarkFromHandle(markerHandle
);
189 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
194 void Document::DeleteAllMarks(int markerNum
) {
195 for (int line
= 0; line
< LinesTotal(); line
++) {
196 static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->DeleteMark(line
, markerNum
, true);
198 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
203 int Document::LineFromHandle(int markerHandle
) {
204 return static_cast<LineMarkers
*>(perLineData
[ldMarkers
])->LineFromHandle(markerHandle
);
207 int Document::LineStart(int line
) const {
208 return cb
.LineStart(line
);
211 int Document::LineEnd(int line
) const {
212 if (line
== LinesTotal() - 1) {
213 return LineStart(line
+ 1);
215 int position
= LineStart(line
+ 1) - 1;
216 // When line terminator is CR+LF, may need to go back one more
217 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
224 int Document::LineFromPosition(int pos
) const {
225 return cb
.LineFromPosition(pos
);
228 int Document::LineEndPosition(int position
) const {
229 return LineEnd(LineFromPosition(position
));
232 bool Document::IsLineEndPosition(int position
) const {
233 return LineEnd(LineFromPosition(position
)) == position
;
236 int Document::VCHomePosition(int position
) const {
237 int line
= LineFromPosition(position
);
238 int startPosition
= LineStart(line
);
239 int endLine
= LineEnd(line
);
240 int startText
= startPosition
;
241 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t'))
243 if (position
== startText
)
244 return startPosition
;
249 int Document::SetLevel(int line
, int level
) {
250 int prev
= static_cast<LineLevels
*>(perLineData
[ldLevels
])->SetLevel(line
, level
, LinesTotal());
252 DocModification
mh(SC_MOD_CHANGEFOLD
| SC_MOD_CHANGEMARKER
,
253 LineStart(line
), 0, 0, 0, line
);
254 mh
.foldLevelNow
= level
;
255 mh
.foldLevelPrev
= prev
;
261 int Document::GetLevel(int line
) const {
262 return static_cast<LineLevels
*>(perLineData
[ldLevels
])->GetLevel(line
);
265 void Document::ClearLevels() {
266 static_cast<LineLevels
*>(perLineData
[ldLevels
])->ClearLevels();
269 static bool IsSubordinate(int levelStart
, int levelTry
) {
270 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
273 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
276 int Document::GetLastChild(int lineParent
, int level
) {
278 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
279 int maxLine
= LinesTotal();
280 int lineMaxSubord
= lineParent
;
281 while (lineMaxSubord
< maxLine
- 1) {
282 EnsureStyledTo(LineStart(lineMaxSubord
+ 2));
283 if (!IsSubordinate(level
, GetLevel(lineMaxSubord
+ 1)))
287 if (lineMaxSubord
> lineParent
) {
288 if (level
> (GetLevel(lineMaxSubord
+ 1) & SC_FOLDLEVELNUMBERMASK
)) {
289 // Have chewed up some whitespace that belongs to a parent so seek back
290 if (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
) {
295 return lineMaxSubord
;
298 int Document::GetFoldParent(int line
) {
299 int level
= GetLevel(line
) & SC_FOLDLEVELNUMBERMASK
;
300 int lineLook
= line
- 1;
301 while ((lineLook
> 0) && (
302 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
303 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
307 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
308 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
315 int Document::ClampPositionIntoDocument(int pos
) {
316 return Platform::Clamp(pos
, 0, Length());
319 bool Document::IsCrLf(int pos
) {
322 if (pos
>= (Length() - 1))
324 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
327 static const int maxBytesInDBCSCharacter
=5;
329 int Document::LenChar(int pos
) {
332 } else if (IsCrLf(pos
)) {
334 } else if (SC_CP_UTF8
== dbcsCodePage
) {
335 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
339 if (ch
>= (0x80 + 0x40 + 0x20 + 0x10))
341 else if (ch
>= (0x80 + 0x40 + 0x20))
343 int lengthDoc
= Length();
344 if ((pos
+ len
) > lengthDoc
)
345 return lengthDoc
-pos
;
348 } else if (dbcsCodePage
) {
349 char mbstr
[maxBytesInDBCSCharacter
+1];
351 for (i
=0; i
<Platform::DBCSCharMaxLength(); i
++) {
352 mbstr
[i
] = cb
.CharAt(pos
+i
);
355 return Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
361 static bool IsTrailByte(int ch
) {
362 return (ch
>= 0x80) && (ch
< (0x80 + 0x40));
365 static int BytesFromLead(int leadByte
) {
366 if (leadByte
> 0xF4) {
367 // Characters longer than 4 bytes not possible in current UTF-8
369 } else if (leadByte
>= 0xF0) {
371 } else if (leadByte
>= 0xE0) {
373 } else if (leadByte
>= 0xC2) {
379 bool Document::InGoodUTF8(int pos
, int &start
, int &end
) {
381 while ((lead
>0) && (pos
-lead
< 4) && IsTrailByte(static_cast<unsigned char>(cb
.CharAt(lead
-1))))
387 int leadByte
= static_cast<unsigned char>(cb
.CharAt(start
));
388 int bytes
= BytesFromLead(leadByte
);
392 int trailBytes
= bytes
- 1;
393 int len
= pos
- lead
+ 1;
394 if (len
> trailBytes
)
395 // pos too far from lead
397 // Check that there are enough trails for this lead
399 while ((trail
-lead
<trailBytes
) && (trail
< Length())) {
400 if (!IsTrailByte(static_cast<unsigned char>(cb
.CharAt(trail
)))) {
410 // Normalise a position so that it is not halfway through a two byte character.
411 // This can occur in two situations -
412 // When lines are terminated with \r\n pairs which should be treated as one character.
413 // When displaying DBCS text such as Japanese.
414 // If moving, move the position in the indicated direction.
415 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
416 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
417 // If out of range, just return minimum/maximum value.
423 // PLATFORM_ASSERT(pos > 0 && pos < Length());
424 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
431 // Not between CR and LF
434 if (SC_CP_UTF8
== dbcsCodePage
) {
435 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
438 if (IsTrailByte(ch
) && InGoodUTF8(pos
, startUTF
, endUTF
)) {
439 // ch is a trail byte within a UTF-8 character
446 // Anchor DBCS calculations at start of line because start of line can
447 // not be a DBCS trail byte.
448 int posCheck
= LineStart(LineFromPosition(pos
));
449 while (posCheck
< pos
) {
450 char mbstr
[maxBytesInDBCSCharacter
+1];
452 for (i
=0; i
<Platform::DBCSCharMaxLength(); i
++) {
453 mbstr
[i
] = cb
.CharAt(posCheck
+i
);
457 int mbsize
= Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
458 if (posCheck
+ mbsize
== pos
) {
460 } else if (posCheck
+ mbsize
> pos
) {
462 return posCheck
+ mbsize
;
475 void Document::ModifiedAt(int pos
) {
480 void Document::CheckReadOnly() {
481 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
482 enteredReadOnlyCount
++;
483 NotifyModifyAttempt();
484 enteredReadOnlyCount
--;
488 // Document only modified by gateways DeleteChars, InsertString, Undo, Redo, and SetStyleAt.
489 // SetStyleAt does not change the persistent state of a document
491 bool Document::DeleteChars(int pos
, int len
) {
494 if ((pos
+ len
) > Length())
497 if (enteredModification
!= 0) {
500 enteredModification
++;
501 if (!cb
.IsReadOnly()) {
504 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
507 int prevLinesTotal
= LinesTotal();
508 bool startSavePoint
= cb
.IsSavePoint();
509 bool startSequence
= false;
510 const char *text
= cb
.DeleteChars(pos
, len
, startSequence
);
511 if (startSavePoint
&& cb
.IsCollectingUndo())
512 NotifySavePoint(!startSavePoint
);
513 if ((pos
< Length()) || (pos
== 0))
519 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
| (startSequence
?SC_STARTACTION
:0),
521 LinesTotal() - prevLinesTotal
, text
));
523 enteredModification
--;
525 return !cb
.IsReadOnly();
529 * Insert a string with a length.
531 bool Document::InsertString(int position
, const char *s
, int insertLength
) {
532 if (insertLength
<= 0) {
536 if (enteredModification
!= 0) {
539 enteredModification
++;
540 if (!cb
.IsReadOnly()) {
543 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
544 position
, insertLength
,
546 int prevLinesTotal
= LinesTotal();
547 bool startSavePoint
= cb
.IsSavePoint();
548 bool startSequence
= false;
549 const char *text
= cb
.InsertString(position
, s
, insertLength
, startSequence
);
550 if (startSavePoint
&& cb
.IsCollectingUndo())
551 NotifySavePoint(!startSavePoint
);
552 ModifiedAt(position
);
555 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
| (startSequence
?SC_STARTACTION
:0),
556 position
, insertLength
,
557 LinesTotal() - prevLinesTotal
, text
));
559 enteredModification
--;
561 return !cb
.IsReadOnly();
564 int Document::Undo() {
567 if (enteredModification
== 0) {
568 enteredModification
++;
569 if (!cb
.IsReadOnly()) {
570 bool startSavePoint
= cb
.IsSavePoint();
571 bool multiLine
= false;
572 int steps
= cb
.StartUndo();
573 //Platform::DebugPrintf("Steps=%d\n", steps);
574 for (int step
= 0; step
< steps
; step
++) {
575 const int prevLinesTotal
= LinesTotal();
576 const Action
&action
= cb
.GetUndoStep();
577 if (action
.at
== removeAction
) {
578 NotifyModified(DocModification(
579 SC_MOD_BEFOREINSERT
| SC_PERFORMED_UNDO
, action
));
580 } else if (action
.at
== containerAction
) {
581 DocModification
dm(SC_MOD_CONTAINER
| SC_PERFORMED_UNDO
);
582 dm
.token
= action
.position
;
585 NotifyModified(DocModification(
586 SC_MOD_BEFOREDELETE
| SC_PERFORMED_UNDO
, action
));
588 cb
.PerformUndoStep();
589 int cellPosition
= action
.position
;
590 if (action
.at
!= containerAction
) {
591 ModifiedAt(cellPosition
);
592 newPos
= cellPosition
;
595 int modFlags
= SC_PERFORMED_UNDO
;
596 // With undo, an insertion action becomes a deletion notification
597 if (action
.at
== removeAction
) {
598 newPos
+= action
.lenData
;
599 modFlags
|= SC_MOD_INSERTTEXT
;
600 } else if (action
.at
== insertAction
) {
601 modFlags
|= SC_MOD_DELETETEXT
;
604 modFlags
|= SC_MULTISTEPUNDOREDO
;
605 const int linesAdded
= LinesTotal() - prevLinesTotal
;
608 if (step
== steps
- 1) {
609 modFlags
|= SC_LASTSTEPINUNDOREDO
;
611 modFlags
|= SC_MULTILINEUNDOREDO
;
613 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
614 linesAdded
, action
.data
));
617 bool endSavePoint
= cb
.IsSavePoint();
618 if (startSavePoint
!= endSavePoint
)
619 NotifySavePoint(endSavePoint
);
621 enteredModification
--;
626 int Document::Redo() {
629 if (enteredModification
== 0) {
630 enteredModification
++;
631 if (!cb
.IsReadOnly()) {
632 bool startSavePoint
= cb
.IsSavePoint();
633 bool multiLine
= false;
634 int steps
= cb
.StartRedo();
635 for (int step
= 0; step
< steps
; step
++) {
636 const int prevLinesTotal
= LinesTotal();
637 const Action
&action
= cb
.GetRedoStep();
638 if (action
.at
== insertAction
) {
639 NotifyModified(DocModification(
640 SC_MOD_BEFOREINSERT
| SC_PERFORMED_REDO
, action
));
641 } else if (action
.at
== containerAction
) {
642 DocModification
dm(SC_MOD_CONTAINER
| SC_PERFORMED_REDO
);
643 dm
.token
= action
.position
;
646 NotifyModified(DocModification(
647 SC_MOD_BEFOREDELETE
| SC_PERFORMED_REDO
, action
));
649 cb
.PerformRedoStep();
650 if (action
.at
!= containerAction
) {
651 ModifiedAt(action
.position
);
652 newPos
= action
.position
;
655 int modFlags
= SC_PERFORMED_REDO
;
656 if (action
.at
== insertAction
) {
657 newPos
+= action
.lenData
;
658 modFlags
|= SC_MOD_INSERTTEXT
;
659 } else if (action
.at
== removeAction
) {
660 modFlags
|= SC_MOD_DELETETEXT
;
663 modFlags
|= SC_MULTISTEPUNDOREDO
;
664 const int linesAdded
= LinesTotal() - prevLinesTotal
;
667 if (step
== steps
- 1) {
668 modFlags
|= SC_LASTSTEPINUNDOREDO
;
670 modFlags
|= SC_MULTILINEUNDOREDO
;
673 DocModification(modFlags
, action
.position
, action
.lenData
,
674 linesAdded
, action
.data
));
677 bool endSavePoint
= cb
.IsSavePoint();
678 if (startSavePoint
!= endSavePoint
)
679 NotifySavePoint(endSavePoint
);
681 enteredModification
--;
687 * Insert a single character.
689 bool Document::InsertChar(int pos
, char ch
) {
692 return InsertString(pos
, chs
, 1);
696 * Insert a null terminated string.
698 bool Document::InsertCString(int position
, const char *s
) {
699 return InsertString(position
, s
, strlen(s
));
702 void Document::ChangeChar(int pos
, char ch
) {
707 void Document::DelChar(int pos
) {
708 DeleteChars(pos
, LenChar(pos
));
711 void Document::DelCharBack(int pos
) {
714 } else if (IsCrLf(pos
- 2)) {
715 DeleteChars(pos
- 2, 2);
716 } else if (dbcsCodePage
) {
717 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
718 DeleteChars(startChar
, pos
- startChar
);
720 DeleteChars(pos
- 1, 1);
724 static bool isindentchar(char ch
) {
725 return (ch
== ' ') || (ch
== '\t');
728 static int NextTab(int pos
, int tabSize
) {
729 return ((pos
/ tabSize
) + 1) * tabSize
;
732 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
733 length
--; // ensure space for \0
735 while ((indent
>= tabSize
) && (length
> 0)) {
741 while ((indent
> 0) && (length
> 0)) {
749 int Document::GetLineIndentation(int line
) {
751 if ((line
>= 0) && (line
< LinesTotal())) {
752 int lineStart
= LineStart(line
);
753 int length
= Length();
754 for (int i
= lineStart
; i
< length
; i
++) {
755 char ch
= cb
.CharAt(i
);
759 indent
= NextTab(indent
, tabInChars
);
767 void Document::SetLineIndentation(int line
, int indent
) {
768 int indentOfLine
= GetLineIndentation(line
);
771 if (indent
!= indentOfLine
) {
773 CreateIndentation(linebuf
, sizeof(linebuf
), indent
, tabInChars
, !useTabs
);
774 int thisLineStart
= LineStart(line
);
775 int indentPos
= GetLineIndentPosition(line
);
777 DeleteChars(thisLineStart
, indentPos
- thisLineStart
);
778 InsertCString(thisLineStart
, linebuf
);
782 int Document::GetLineIndentPosition(int line
) const {
785 int pos
= LineStart(line
);
786 int length
= Length();
787 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
793 int Document::GetColumn(int pos
) {
795 int line
= LineFromPosition(pos
);
796 if ((line
>= 0) && (line
< LinesTotal())) {
797 for (int i
= LineStart(line
); i
< pos
;) {
798 char ch
= cb
.CharAt(i
);
800 column
= NextTab(column
, tabInChars
);
802 } else if (ch
== '\r') {
804 } else if (ch
== '\n') {
806 } else if (i
>= Length()) {
810 i
= MovePositionOutsideChar(i
+ 1, 1, false);
817 int Document::FindColumn(int line
, int column
) {
818 int position
= LineStart(line
);
819 if ((line
>= 0) && (line
< LinesTotal())) {
820 int columnCurrent
= 0;
821 while ((columnCurrent
< column
) && (position
< Length())) {
822 char ch
= cb
.CharAt(position
);
824 columnCurrent
= NextTab(columnCurrent
, tabInChars
);
826 } else if (ch
== '\r') {
828 } else if (ch
== '\n') {
832 position
= MovePositionOutsideChar(position
+ 1, 1, false);
839 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
840 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
841 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
842 int indentOfLine
= GetLineIndentation(line
);
844 if (LineStart(line
) < LineEnd(line
)) {
845 SetLineIndentation(line
, indentOfLine
+ IndentSize());
848 SetLineIndentation(line
, indentOfLine
- IndentSize());
853 // Convert line endings for a piece of text to a particular mode.
854 // Stop at len or when a NUL is found.
855 // Caller must delete the returned pointer.
856 char *Document::TransformLineEnds(int *pLenOut
, const char *s
, size_t len
, int eolMode
) {
857 char *dest
= new char[2 * len
+ 1];
858 const char *sptr
= s
;
860 for (size_t i
= 0; (i
< len
) && (*sptr
!= '\0'); i
++) {
861 if (*sptr
== '\n' || *sptr
== '\r') {
862 if (eolMode
== SC_EOL_CR
) {
864 } else if (eolMode
== SC_EOL_LF
) {
866 } else { // eolMode == SC_EOL_CRLF
870 if ((*sptr
== '\r') && (i
+1 < len
) && (*(sptr
+1) == '\n')) {
880 *pLenOut
= (dptr
- dest
) - 1;
884 void Document::ConvertLineEnds(int eolModeSet
) {
887 for (int pos
= 0; pos
< Length(); pos
++) {
888 if (cb
.CharAt(pos
) == '\r') {
889 if (cb
.CharAt(pos
+ 1) == '\n') {
891 if (eolModeSet
== SC_EOL_CR
) {
892 DeleteChars(pos
+ 1, 1); // Delete the LF
893 } else if (eolModeSet
== SC_EOL_LF
) {
894 DeleteChars(pos
, 1); // Delete the CR
900 if (eolModeSet
== SC_EOL_CRLF
) {
901 InsertString(pos
+ 1, "\n", 1); // Insert LF
903 } else if (eolModeSet
== SC_EOL_LF
) {
904 InsertString(pos
, "\n", 1); // Insert LF
905 DeleteChars(pos
+ 1, 1); // Delete CR
908 } else if (cb
.CharAt(pos
) == '\n') {
910 if (eolModeSet
== SC_EOL_CRLF
) {
911 InsertString(pos
, "\r", 1); // Insert CR
913 } else if (eolModeSet
== SC_EOL_CR
) {
914 InsertString(pos
, "\r", 1); // Insert CR
915 DeleteChars(pos
+ 1, 1); // Delete LF
922 bool Document::IsWhiteLine(int line
) const {
923 int currentChar
= LineStart(line
);
924 int endLine
= LineEnd(line
);
925 while (currentChar
< endLine
) {
926 if (cb
.CharAt(currentChar
) != ' ' && cb
.CharAt(currentChar
) != '\t') {
934 int Document::ParaUp(int pos
) {
935 int line
= LineFromPosition(pos
);
937 while (line
>= 0 && IsWhiteLine(line
)) { // skip empty lines
940 while (line
>= 0 && !IsWhiteLine(line
)) { // skip non-empty lines
944 return LineStart(line
);
947 int Document::ParaDown(int pos
) {
948 int line
= LineFromPosition(pos
);
949 while (line
< LinesTotal() && !IsWhiteLine(line
)) { // skip non-empty lines
952 while (line
< LinesTotal() && IsWhiteLine(line
)) { // skip empty lines
955 if (line
< LinesTotal())
956 return LineStart(line
);
957 else // end of a document
958 return LineEnd(line
-1);
961 CharClassify::cc
Document::WordCharClass(unsigned char ch
) {
962 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
>= 0x80))
963 return CharClassify::ccWord
;
964 return charClass
.GetClass(ch
);
968 * Used by commmands that want to select whole words.
969 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
971 int Document::ExtendWordSelect(int pos
, int delta
, bool onlyWordCharacters
) {
972 CharClassify::cc ccStart
= CharClassify::ccWord
;
974 if (!onlyWordCharacters
)
975 ccStart
= WordCharClass(cb
.CharAt(pos
-1));
976 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
))
979 if (!onlyWordCharacters
&& pos
< Length())
980 ccStart
= WordCharClass(cb
.CharAt(pos
));
981 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
984 return MovePositionOutsideChar(pos
, delta
, true);
988 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
990 * This is looking for a transition between character classes although there is also some
991 * additional movement to transit white space.
992 * Used by cursor movement by word commands.
994 int Document::NextWordStart(int pos
, int delta
) {
996 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == CharClassify::ccSpace
))
999 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
-1));
1000 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
)) {
1005 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
));
1006 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
1008 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == CharClassify::ccSpace
))
1015 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
1017 * This is looking for a transition between character classes although there is also some
1018 * additional movement to transit white space.
1019 * Used by cursor movement by word commands.
1021 int Document::NextWordEnd(int pos
, int delta
) {
1024 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
-1));
1025 if (ccStart
!= CharClassify::ccSpace
) {
1026 while (pos
> 0 && WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
) {
1030 while (pos
> 0 && WordCharClass(cb
.CharAt(pos
- 1)) == CharClassify::ccSpace
) {
1035 while (pos
< Length() && WordCharClass(cb
.CharAt(pos
)) == CharClassify::ccSpace
) {
1038 if (pos
< Length()) {
1039 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
));
1040 while (pos
< Length() && WordCharClass(cb
.CharAt(pos
)) == ccStart
) {
1049 * Check that the character at the given position is a word or punctuation character and that
1050 * the previous character is of a different character class.
1052 bool Document::IsWordStartAt(int pos
) {
1054 CharClassify::cc ccPos
= WordCharClass(CharAt(pos
));
1055 return (ccPos
== CharClassify::ccWord
|| ccPos
== CharClassify::ccPunctuation
) &&
1056 (ccPos
!= WordCharClass(CharAt(pos
- 1)));
1062 * Check that the character at the given position is a word or punctuation character and that
1063 * the next character is of a different character class.
1065 bool Document::IsWordEndAt(int pos
) {
1066 if (pos
< Length()) {
1067 CharClassify::cc ccPrev
= WordCharClass(CharAt(pos
-1));
1068 return (ccPrev
== CharClassify::ccWord
|| ccPrev
== CharClassify::ccPunctuation
) &&
1069 (ccPrev
!= WordCharClass(CharAt(pos
)));
1075 * Check that the given range is has transitions between character classes at both
1076 * ends and where the characters on the inside are word or punctuation characters.
1078 bool Document::IsWordAt(int start
, int end
) {
1079 return IsWordStartAt(start
) && IsWordEndAt(end
);
1082 static inline char MakeLowerCase(char ch
) {
1083 if (ch
< 'A' || ch
> 'Z')
1086 return static_cast<char>(ch
- 'A' + 'a');
1089 static bool GoodTrailByte(int v
) {
1090 return (v
>= 0x80) && (v
< 0xc0);
1093 size_t Document::ExtractChar(int pos
, char *bytes
) {
1094 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
1095 size_t widthChar
= UTF8CharLength(ch
);
1097 for (size_t i
=1; i
<widthChar
; i
++) {
1098 bytes
[i
] = cb
.CharAt(pos
+i
);
1099 if (!GoodTrailByte(static_cast<unsigned char>(bytes
[i
]))) { // Bad byte
1106 CaseFolderTable::CaseFolderTable() {
1107 for (size_t iChar
=0; iChar
<sizeof(mapping
); iChar
++) {
1108 mapping
[iChar
] = static_cast<char>(iChar
);
1112 CaseFolderTable::~CaseFolderTable() {
1115 size_t CaseFolderTable::Fold(char *folded
, size_t sizeFolded
, const char *mixed
, size_t lenMixed
) {
1116 if (lenMixed
> sizeFolded
) {
1119 for (size_t i
=0; i
<lenMixed
; i
++) {
1120 folded
[i
] = mapping
[static_cast<unsigned char>(mixed
[i
])];
1126 void CaseFolderTable::SetTranslation(char ch
, char chTranslation
) {
1127 mapping
[static_cast<unsigned char>(ch
)] = chTranslation
;
1130 void CaseFolderTable::StandardASCII() {
1131 for (size_t iChar
=0; iChar
<sizeof(mapping
); iChar
++) {
1132 if (iChar
>= 'A' && iChar
<= 'Z') {
1133 mapping
[iChar
] = static_cast<char>(iChar
- 'A' + 'a');
1135 mapping
[iChar
] = static_cast<char>(iChar
);
1140 bool Document::MatchesWordOptions(bool word
, bool wordStart
, int pos
, int length
) {
1141 return (!word
&& !wordStart
) ||
1142 (word
&& IsWordAt(pos
, pos
+ length
)) ||
1143 (wordStart
&& IsWordStartAt(pos
));
1147 * Find text in document, supporting both forward and backward
1148 * searches (just pass minPos > maxPos to do a backward search)
1149 * Has not been tested with backwards DBCS searches yet.
1151 long Document::FindText(int minPos
, int maxPos
, const char *search
,
1152 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
, int flags
,
1153 int *length
, CaseFolder
*pcf
) {
1156 regex
= CreateRegexSearch(&charClass
);
1157 return regex
->FindText(this, minPos
, maxPos
, search
, caseSensitive
, word
, wordStart
, flags
, length
);
1160 const bool forward
= minPos
<= maxPos
;
1161 const int increment
= forward
? 1 : -1;
1163 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1164 const int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
1165 const int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
1167 // Compute actual search ranges needed
1168 const int lengthFind
= (*length
== -1) ? static_cast<int>(strlen(search
)) : *length
;
1169 const int endSearch
= (startPos
<= endPos
) ? endPos
- lengthFind
+ 1 : endPos
;
1171 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
1172 const int limitPos
= Platform::Maximum(startPos
, endPos
);
1173 int pos
= forward
? startPos
: (startPos
- 1);
1174 if (caseSensitive
) {
1175 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
1176 bool found
= (pos
+ lengthFind
) <= limitPos
;
1177 for (int indexSearch
= 0; (indexSearch
< lengthFind
) && found
; indexSearch
++) {
1178 found
= CharAt(pos
+ indexSearch
) == search
[indexSearch
];
1180 if (found
&& MatchesWordOptions(word
, wordStart
, pos
, lengthFind
)) {
1184 if (dbcsCodePage
&& (pos
>= 0)) {
1185 // Have to use >= 0 as otherwise next statement would change
1186 // -1 to 0 and make loop infinite.
1187 // Ensure trying to match from start of character
1188 pos
= MovePositionOutsideChar(pos
, increment
, false);
1191 } else if (SC_CP_UTF8
== dbcsCodePage
) {
1192 const size_t maxBytesCharacter
= 4;
1193 const size_t maxFoldingExpansion
= 4;
1194 std::vector
<char> searchThing(lengthFind
* maxBytesCharacter
* maxFoldingExpansion
+ 1);
1195 const int lenSearch
= pcf
->Fold(&searchThing
[0], searchThing
.size(), search
, lengthFind
);
1196 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
1197 int widthFirstCharacter
= 0;
1198 int indexDocument
= 0;
1199 int indexSearch
= 0;
1200 bool characterMatches
= true;
1201 while (characterMatches
&&
1202 ((pos
+ indexDocument
) < limitPos
) &&
1203 (indexSearch
< lenSearch
)) {
1204 char bytes
[maxBytesCharacter
+ 1];
1205 bytes
[maxBytesCharacter
] = 0;
1206 const int widthChar
= ExtractChar(pos
+ indexDocument
, bytes
);
1207 if (!widthFirstCharacter
)
1208 widthFirstCharacter
= widthChar
;
1209 char folded
[maxBytesCharacter
* maxFoldingExpansion
+ 1];
1210 const int lenFlat
= pcf
->Fold(folded
, sizeof(folded
), bytes
, widthChar
);
1211 folded
[lenFlat
] = 0;
1212 // Does folded match the buffer
1213 characterMatches
= 0 == memcmp(folded
, &searchThing
[0] + indexSearch
, lenFlat
);
1214 indexDocument
+= widthChar
;
1215 indexSearch
+= lenFlat
;
1217 if (characterMatches
&& (indexSearch
== static_cast<int>(lenSearch
))) {
1218 if (MatchesWordOptions(word
, wordStart
, pos
, indexDocument
)) {
1219 *length
= indexDocument
;
1224 pos
+= widthFirstCharacter
;
1228 // Ensure trying to match from start of character
1229 pos
= MovePositionOutsideChar(pos
, increment
, false);
1234 CaseFolderTable caseFolder
;
1235 std::vector
<char> searchThing(lengthFind
+ 1);
1236 pcf
->Fold(&searchThing
[0], searchThing
.size(), search
, lengthFind
);
1237 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
1238 bool found
= (pos
+ lengthFind
) <= limitPos
;
1239 for (int indexSearch
= 0; (indexSearch
< lengthFind
) && found
; indexSearch
++) {
1240 char ch
= CharAt(pos
+ indexSearch
);
1242 pcf
->Fold(folded
, sizeof(folded
), &ch
, 1);
1243 found
= folded
[0] == searchThing
[indexSearch
];
1245 if (found
&& MatchesWordOptions(word
, wordStart
, pos
, lengthFind
)) {
1249 if (dbcsCodePage
&& (pos
>= 0)) {
1250 // Ensure trying to match from start of character
1251 pos
= MovePositionOutsideChar(pos
, increment
, false);
1256 //Platform::DebugPrintf("Not found\n");
1260 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
1261 return regex
->SubstituteByPosition(this, text
, length
);
1264 int Document::LinesTotal() const {
1268 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
1269 for (int pos
= r
.start
; pos
< r
.end
;) {
1270 int len
= LenChar(pos
);
1272 char ch
= CharAt(pos
);
1273 if (makeUpperCase
) {
1274 if (IsLowerCase(ch
)) {
1275 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1278 if (IsUpperCase(ch
)) {
1279 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1287 void Document::SetDefaultCharClasses(bool includeWordClass
) {
1288 charClass
.SetDefaultCharClasses(includeWordClass
);
1291 void Document::SetCharClasses(const unsigned char *chars
, CharClassify::cc newCharClass
) {
1292 charClass
.SetCharClasses(chars
, newCharClass
);
1295 void Document::SetStylingBits(int bits
) {
1297 stylingBitsMask
= (1 << stylingBits
) - 1;
1300 void Document::StartStyling(int position
, char mask
) {
1302 endStyled
= position
;
1305 bool Document::SetStyleFor(int length
, char style
) {
1306 if (enteredStyling
!= 0) {
1310 style
&= stylingMask
;
1311 int prevEndStyled
= endStyled
;
1312 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1313 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1314 prevEndStyled
, length
);
1317 endStyled
+= length
;
1323 bool Document::SetStyles(int length
, const char *styles
) {
1324 if (enteredStyling
!= 0) {
1328 bool didChange
= false;
1331 for (int iPos
= 0; iPos
< length
; iPos
++, endStyled
++) {
1332 PLATFORM_ASSERT(endStyled
< Length());
1333 if (cb
.SetStyleAt(endStyled
, styles
[iPos
], stylingMask
)) {
1335 startMod
= endStyled
;
1342 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1343 startMod
, endMod
- startMod
+ 1);
1351 void Document::EnsureStyledTo(int pos
) {
1352 if ((enteredStyling
== 0) && (pos
> GetEndStyled())) {
1353 IncrementStyleClock();
1354 // Ask the watchers to style, and stop as soon as one responds.
1355 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++) {
1356 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
1361 int Document::SetLineState(int line
, int state
) {
1362 int statePrevious
= static_cast<LineState
*>(perLineData
[ldState
])->SetLineState(line
, state
);
1363 if (state
!= statePrevious
) {
1364 DocModification
mh(SC_MOD_CHANGELINESTATE
, 0, 0, 0, 0, line
);
1367 return statePrevious
;
1370 int Document::GetLineState(int line
) const {
1371 return static_cast<LineState
*>(perLineData
[ldState
])->GetLineState(line
);
1374 int Document::GetMaxLineState() {
1375 return static_cast<LineState
*>(perLineData
[ldState
])->GetMaxLineState();
1378 StyledText
Document::MarginStyledText(int line
) {
1379 LineAnnotation
*pla
= static_cast<LineAnnotation
*>(perLineData
[ldMargin
]);
1380 return StyledText(pla
->Length(line
), pla
->Text(line
),
1381 pla
->MultipleStyles(line
), pla
->Style(line
), pla
->Styles(line
));
1384 void Document::MarginSetText(int line
, const char *text
) {
1385 static_cast<LineAnnotation
*>(perLineData
[ldMargin
])->SetText(line
, text
);
1386 DocModification
mh(SC_MOD_CHANGEMARGIN
, LineStart(line
), 0, 0, 0, line
);
1390 void Document::MarginSetStyle(int line
, int style
) {
1391 static_cast<LineAnnotation
*>(perLineData
[ldMargin
])->SetStyle(line
, style
);
1394 void Document::MarginSetStyles(int line
, const unsigned char *styles
) {
1395 static_cast<LineAnnotation
*>(perLineData
[ldMargin
])->SetStyles(line
, styles
);
1398 int Document::MarginLength(int line
) const {
1399 return static_cast<LineAnnotation
*>(perLineData
[ldMargin
])->Length(line
);
1402 void Document::MarginClearAll() {
1403 int maxEditorLine
= LinesTotal();
1404 for (int l
=0; l
<maxEditorLine
; l
++)
1405 MarginSetText(l
, 0);
1406 // Free remaining data
1407 static_cast<LineAnnotation
*>(perLineData
[ldMargin
])->ClearAll();
1410 bool Document::AnnotationAny() const {
1411 return static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->AnySet();
1414 StyledText
Document::AnnotationStyledText(int line
) {
1415 LineAnnotation
*pla
= static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
]);
1416 return StyledText(pla
->Length(line
), pla
->Text(line
),
1417 pla
->MultipleStyles(line
), pla
->Style(line
), pla
->Styles(line
));
1420 void Document::AnnotationSetText(int line
, const char *text
) {
1421 const int linesBefore
= AnnotationLines(line
);
1422 static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->SetText(line
, text
);
1423 const int linesAfter
= AnnotationLines(line
);
1424 DocModification
mh(SC_MOD_CHANGEANNOTATION
, LineStart(line
), 0, 0, 0, line
);
1425 mh
.annotationLinesAdded
= linesAfter
- linesBefore
;
1429 void Document::AnnotationSetStyle(int line
, int style
) {
1430 static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->SetStyle(line
, style
);
1433 void Document::AnnotationSetStyles(int line
, const unsigned char *styles
) {
1434 static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->SetStyles(line
, styles
);
1437 int Document::AnnotationLength(int line
) const {
1438 return static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->Length(line
);
1441 int Document::AnnotationLines(int line
) const {
1442 return static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->Lines(line
);
1445 void Document::AnnotationClearAll() {
1446 int maxEditorLine
= LinesTotal();
1447 for (int l
=0; l
<maxEditorLine
; l
++)
1448 AnnotationSetText(l
, 0);
1449 // Free remaining data
1450 static_cast<LineAnnotation
*>(perLineData
[ldAnnotation
])->ClearAll();
1453 void Document::IncrementStyleClock() {
1454 styleClock
= (styleClock
+ 1) % 0x100000;
1457 void Document::DecorationFillRange(int position
, int value
, int fillLength
) {
1458 if (decorations
.FillRange(position
, value
, fillLength
)) {
1459 DocModification
mh(SC_MOD_CHANGEINDICATOR
| SC_PERFORMED_USER
,
1460 position
, fillLength
);
1465 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
1466 for (int i
= 0; i
< lenWatchers
; i
++) {
1467 if ((watchers
[i
].watcher
== watcher
) &&
1468 (watchers
[i
].userData
== userData
))
1471 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1472 for (int j
= 0; j
< lenWatchers
; j
++)
1473 pwNew
[j
] = watchers
[j
];
1474 pwNew
[lenWatchers
].watcher
= watcher
;
1475 pwNew
[lenWatchers
].userData
= userData
;
1482 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
1483 for (int i
= 0; i
< lenWatchers
; i
++) {
1484 if ((watchers
[i
].watcher
== watcher
) &&
1485 (watchers
[i
].userData
== userData
)) {
1486 if (lenWatchers
== 1) {
1491 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1492 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1493 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1505 void Document::NotifyModifyAttempt() {
1506 for (int i
= 0; i
< lenWatchers
; i
++) {
1507 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1511 void Document::NotifySavePoint(bool atSavePoint
) {
1512 for (int i
= 0; i
< lenWatchers
; i
++) {
1513 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1517 void Document::NotifyModified(DocModification mh
) {
1518 if (mh
.modificationType
& SC_MOD_INSERTTEXT
) {
1519 decorations
.InsertSpace(mh
.position
, mh
.length
);
1520 } else if (mh
.modificationType
& SC_MOD_DELETETEXT
) {
1521 decorations
.DeleteRange(mh
.position
, mh
.length
);
1523 for (int i
= 0; i
< lenWatchers
; i
++) {
1524 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1528 bool Document::IsWordPartSeparator(char ch
) {
1529 return (WordCharClass(ch
) == CharClassify::ccWord
) && IsPunctuation(ch
);
1532 int Document::WordPartLeft(int pos
) {
1535 char startChar
= cb
.CharAt(pos
);
1536 if (IsWordPartSeparator(startChar
)) {
1537 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1542 startChar
= cb
.CharAt(pos
);
1544 if (IsLowerCase(startChar
)) {
1545 while (pos
> 0 && IsLowerCase(cb
.CharAt(pos
)))
1547 if (!IsUpperCase(cb
.CharAt(pos
)) && !IsLowerCase(cb
.CharAt(pos
)))
1549 } else if (IsUpperCase(startChar
)) {
1550 while (pos
> 0 && IsUpperCase(cb
.CharAt(pos
)))
1552 if (!IsUpperCase(cb
.CharAt(pos
)))
1554 } else if (IsADigit(startChar
)) {
1555 while (pos
> 0 && IsADigit(cb
.CharAt(pos
)))
1557 if (!IsADigit(cb
.CharAt(pos
)))
1559 } else if (IsPunctuation(startChar
)) {
1560 while (pos
> 0 && IsPunctuation(cb
.CharAt(pos
)))
1562 if (!IsPunctuation(cb
.CharAt(pos
)))
1564 } else if (isspacechar(startChar
)) {
1565 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1567 if (!isspacechar(cb
.CharAt(pos
)))
1569 } else if (!isascii(startChar
)) {
1570 while (pos
> 0 && !isascii(cb
.CharAt(pos
)))
1572 if (isascii(cb
.CharAt(pos
)))
1582 int Document::WordPartRight(int pos
) {
1583 char startChar
= cb
.CharAt(pos
);
1584 int length
= Length();
1585 if (IsWordPartSeparator(startChar
)) {
1586 while (pos
< length
&& IsWordPartSeparator(cb
.CharAt(pos
)))
1588 startChar
= cb
.CharAt(pos
);
1590 if (!isascii(startChar
)) {
1591 while (pos
< length
&& !isascii(cb
.CharAt(pos
)))
1593 } else if (IsLowerCase(startChar
)) {
1594 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1596 } else if (IsUpperCase(startChar
)) {
1597 if (IsLowerCase(cb
.CharAt(pos
+ 1))) {
1599 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1602 while (pos
< length
&& IsUpperCase(cb
.CharAt(pos
)))
1605 if (IsLowerCase(cb
.CharAt(pos
)) && IsUpperCase(cb
.CharAt(pos
- 1)))
1607 } else if (IsADigit(startChar
)) {
1608 while (pos
< length
&& IsADigit(cb
.CharAt(pos
)))
1610 } else if (IsPunctuation(startChar
)) {
1611 while (pos
< length
&& IsPunctuation(cb
.CharAt(pos
)))
1613 } else if (isspacechar(startChar
)) {
1614 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))
1622 bool IsLineEndChar(char c
) {
1623 return (c
== '\n' || c
== '\r');
1626 int Document::ExtendStyleRange(int pos
, int delta
, bool singleLine
) {
1627 int sStart
= cb
.StyleAt(pos
);
1629 while (pos
> 0 && (cb
.StyleAt(pos
) == sStart
) && (!singleLine
|| !IsLineEndChar(cb
.CharAt(pos
))))
1633 while (pos
< (Length()) && (cb
.StyleAt(pos
) == sStart
) && (!singleLine
|| !IsLineEndChar(cb
.CharAt(pos
))))
1639 static char BraceOpposite(char ch
) {
1662 // TODO: should be able to extend styled region to find matching brace
1663 int Document::BraceMatch(int position
, int /*maxReStyle*/) {
1664 char chBrace
= CharAt(position
);
1665 char chSeek
= BraceOpposite(chBrace
);
1668 char styBrace
= static_cast<char>(StyleAt(position
) & stylingBitsMask
);
1670 if (chBrace
== '(' || chBrace
== '[' || chBrace
== '{' || chBrace
== '<')
1673 position
= position
+ direction
;
1674 while ((position
>= 0) && (position
< Length())) {
1675 position
= MovePositionOutsideChar(position
, direction
, true);
1676 char chAtPos
= CharAt(position
);
1677 char styAtPos
= static_cast<char>(StyleAt(position
) & stylingBitsMask
);
1678 if ((position
> GetEndStyled()) || (styAtPos
== styBrace
)) {
1679 if (chAtPos
== chBrace
)
1681 if (chAtPos
== chSeek
)
1686 position
= position
+ direction
;
1692 * Implementation of RegexSearchBase for the default built-in regular expression engine
1694 class BuiltinRegex
: public RegexSearchBase
{
1696 BuiltinRegex(CharClassify
*charClassTable
) : search(charClassTable
), substituted(NULL
) {}
1698 virtual ~BuiltinRegex() {
1702 virtual long FindText(Document
*doc
, int minPos
, int maxPos
, const char *s
,
1703 bool caseSensitive
, bool word
, bool wordStart
, int flags
,
1706 virtual const char *SubstituteByPosition(Document
*doc
, const char *text
, int *length
);
1713 // Define a way for the Regular Expression code to access the document
1714 class DocumentIndexer
: public CharacterIndexer
{
1718 DocumentIndexer(Document
*pdoc_
, int end_
) :
1719 pdoc(pdoc_
), end(end_
) {
1722 virtual ~DocumentIndexer() {
1725 virtual char CharAt(int index
) {
1726 if (index
< 0 || index
>= end
)
1729 return pdoc
->CharAt(index
);
1733 long BuiltinRegex::FindText(Document
*doc
, int minPos
, int maxPos
, const char *s
,
1734 bool caseSensitive
, bool, bool, int flags
,
1736 bool posix
= (flags
& SCFIND_POSIX
) != 0;
1737 int increment
= (minPos
<= maxPos
) ? 1 : -1;
1739 int startPos
= minPos
;
1740 int endPos
= maxPos
;
1742 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1743 startPos
= doc
->MovePositionOutsideChar(startPos
, 1, false);
1744 endPos
= doc
->MovePositionOutsideChar(endPos
, 1, false);
1746 const char *errmsg
= search
.Compile(s
, *length
, caseSensitive
, posix
);
1750 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
1751 // Replace first '.' with '-' in each property file variable reference:
1752 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
1753 // Replace: $(\1-\2)
1754 int lineRangeStart
= doc
->LineFromPosition(startPos
);
1755 int lineRangeEnd
= doc
->LineFromPosition(endPos
);
1756 if ((increment
== 1) &&
1757 (startPos
>= doc
->LineEnd(lineRangeStart
)) &&
1758 (lineRangeStart
< lineRangeEnd
)) {
1759 // the start position is at end of line or between line end characters.
1761 startPos
= doc
->LineStart(lineRangeStart
);
1765 char searchEnd
= s
[*length
- 1];
1766 int lineRangeBreak
= lineRangeEnd
+ increment
;
1767 for (int line
= lineRangeStart
; line
!= lineRangeBreak
; line
+= increment
) {
1768 int startOfLine
= doc
->LineStart(line
);
1769 int endOfLine
= doc
->LineEnd(line
);
1770 if (increment
== 1) {
1771 if (line
== lineRangeStart
) {
1772 if ((startPos
!= startOfLine
) && (s
[0] == '^'))
1773 continue; // Can't match start of line if start position after start of line
1774 startOfLine
= startPos
;
1776 if (line
== lineRangeEnd
) {
1777 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
1778 continue; // Can't match end of line if end position before end of line
1782 if (line
== lineRangeEnd
) {
1783 if ((endPos
!= startOfLine
) && (s
[0] == '^'))
1784 continue; // Can't match start of line if end position after start of line
1785 startOfLine
= endPos
;
1787 if (line
== lineRangeStart
) {
1788 if ((startPos
!= endOfLine
) && (searchEnd
== '$'))
1789 continue; // Can't match end of line if start position before end of line
1790 endOfLine
= startPos
;
1794 DocumentIndexer
di(doc
, endOfLine
);
1795 int success
= search
.Execute(di
, startOfLine
, endOfLine
);
1797 pos
= search
.bopat
[0];
1798 lenRet
= search
.eopat
[0] - search
.bopat
[0];
1799 if (increment
== -1) {
1800 // Check for the last match on this line.
1801 int repetitions
= 1000; // Break out of infinite loop
1802 while (success
&& (search
.eopat
[0] <= endOfLine
) && (repetitions
--)) {
1803 success
= search
.Execute(di
, pos
+1, endOfLine
);
1805 if (search
.eopat
[0] <= minPos
) {
1806 pos
= search
.bopat
[0];
1807 lenRet
= search
.eopat
[0] - search
.bopat
[0];
1821 const char *BuiltinRegex::SubstituteByPosition(Document
*doc
, const char *text
, int *length
) {
1822 delete []substituted
;
1824 DocumentIndexer
di(doc
, doc
->Length());
1825 if (!search
.GrabMatches(di
))
1827 unsigned int lenResult
= 0;
1828 for (int i
= 0; i
< *length
; i
++) {
1829 if (text
[i
] == '\\') {
1830 if (text
[i
+ 1] >= '1' && text
[i
+ 1] <= '9') {
1831 unsigned int patNum
= text
[i
+ 1] - '0';
1832 lenResult
+= search
.eopat
[patNum
] - search
.bopat
[patNum
];
1835 switch (text
[i
+ 1]) {
1852 substituted
= new char[lenResult
+ 1];
1853 char *o
= substituted
;
1854 for (int j
= 0; j
< *length
; j
++) {
1855 if (text
[j
] == '\\') {
1856 if (text
[j
+ 1] >= '1' && text
[j
+ 1] <= '9') {
1857 unsigned int patNum
= text
[j
+ 1] - '0';
1858 unsigned int len
= search
.eopat
[patNum
] - search
.bopat
[patNum
];
1859 if (search
.pat
[patNum
]) // Will be null if try for a match that did not occur
1860 memcpy(o
, search
.pat
[patNum
], len
);
1900 *length
= lenResult
;
1904 #ifndef SCI_OWNREGEX
1906 #ifdef SCI_NAMESPACE
1908 RegexSearchBase
*Scintilla::CreateRegexSearch(CharClassify
*charClassTable
) {
1909 return new BuiltinRegex(charClassTable
);
1914 RegexSearchBase
*CreateRegexSearch(CharClassify
*charClassTable
) {
1915 return new BuiltinRegex(charClassTable
);