]>
git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
ff8d0fbcfcb374c404399f7adb74c615b6ebc0f0
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"
16 #include "SplitVector.h"
17 #include "Partitioning.h"
18 #include "RunStyles.h"
19 #include "CellBuffer.h"
20 #include "CharClassify.h"
21 #include "Decoration.h"
26 using namespace Scintilla
;
29 // This is ASCII specific but is safe with chars >= 0x80
30 static inline bool isspacechar(unsigned char ch
) {
31 return (ch
== ' ') || ((ch
>= 0x09) && (ch
<= 0x0d));
34 static inline bool IsPunctuation(char ch
) {
35 return isascii(ch
) && ispunct(ch
);
38 static inline bool IsADigit(char ch
) {
39 return isascii(ch
) && isdigit(ch
);
42 static inline bool IsLowerCase(char ch
) {
43 return isascii(ch
) && islower(ch
);
46 static inline bool IsUpperCase(char ch
) {
47 return isascii(ch
) && isupper(ch
);
50 Document::Document() {
55 eolMode
= SC_EOL_CRLF
;
59 stylingBitsMask
= 0x1F;
63 enteredModification
= 0;
65 enteredReadOnlyCount
= 0;
68 actualIndentInChars
= 8;
71 backspaceUnindents
= false;
80 Document::~Document() {
81 for (int i
= 0; i
< lenWatchers
; i
++) {
82 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
93 // Increase reference count and return its previous value.
94 int Document::AddRef() {
98 // Decrease reference count and return its previous value.
99 // Delete the document if reference count reaches zero.
100 int Document::Release() {
101 int curRefCount
= --refCount
;
102 if (curRefCount
== 0)
107 void Document::SetSavePoint() {
109 NotifySavePoint(true);
112 int Document::AddMark(int line
, int markerNum
) {
113 int prev
= cb
.AddMark(line
, markerNum
);
114 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
119 void Document::AddMarkSet(int line
, int valueSet
) {
120 unsigned int m
= valueSet
;
121 for (int i
= 0; m
; i
++, m
>>= 1)
124 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
128 void Document::DeleteMark(int line
, int markerNum
) {
129 cb
.DeleteMark(line
, markerNum
);
130 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0, line
);
134 void Document::DeleteMarkFromHandle(int markerHandle
) {
135 cb
.DeleteMarkFromHandle(markerHandle
);
136 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
141 void Document::DeleteAllMarks(int markerNum
) {
142 cb
.DeleteAllMarks(markerNum
);
143 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
148 int Document::LineStart(int line
) const {
149 return cb
.LineStart(line
);
152 int Document::LineEnd(int line
) const {
153 if (line
== LinesTotal() - 1) {
154 return LineStart(line
+ 1);
156 int position
= LineStart(line
+ 1) - 1;
157 // When line terminator is CR+LF, may need to go back one more
158 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
165 int Document::LineFromPosition(int pos
) {
166 return cb
.LineFromPosition(pos
);
169 int Document::LineEndPosition(int position
) {
170 return LineEnd(LineFromPosition(position
));
173 int Document::VCHomePosition(int position
) {
174 int line
= LineFromPosition(position
);
175 int startPosition
= LineStart(line
);
176 int endLine
= LineStart(line
+ 1) - 1;
177 int startText
= startPosition
;
178 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t' ) )
180 if (position
== startText
)
181 return startPosition
;
186 int Document::SetLevel(int line
, int level
) {
187 int prev
= cb
.SetLevel(line
, level
);
189 DocModification
mh(SC_MOD_CHANGEFOLD
| SC_MOD_CHANGEMARKER
,
190 LineStart(line
), 0, 0, 0, line
);
191 mh
.foldLevelNow
= level
;
192 mh
.foldLevelPrev
= prev
;
198 static bool IsSubordinate(int levelStart
, int levelTry
) {
199 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
202 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
205 int Document::GetLastChild(int lineParent
, int level
) {
207 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
208 int maxLine
= LinesTotal();
209 int lineMaxSubord
= lineParent
;
210 while (lineMaxSubord
< maxLine
- 1) {
211 EnsureStyledTo(LineStart(lineMaxSubord
+ 2));
212 if (!IsSubordinate(level
, GetLevel(lineMaxSubord
+ 1)))
216 if (lineMaxSubord
> lineParent
) {
217 if (level
> (GetLevel(lineMaxSubord
+ 1) & SC_FOLDLEVELNUMBERMASK
)) {
218 // Have chewed up some whitespace that belongs to a parent so seek back
219 if (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
) {
224 return lineMaxSubord
;
227 int Document::GetFoldParent(int line
) {
228 int level
= GetLevel(line
) & SC_FOLDLEVELNUMBERMASK
;
229 int lineLook
= line
- 1;
230 while ((lineLook
> 0) && (
231 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
232 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
236 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
237 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
244 int Document::ClampPositionIntoDocument(int pos
) {
245 return Platform::Clamp(pos
, 0, Length());
248 bool Document::IsCrLf(int pos
) {
251 if (pos
>= (Length() - 1))
253 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
256 static const int maxBytesInDBCSCharacter
=5;
258 int Document::LenChar(int pos
) {
261 } else if (IsCrLf(pos
)) {
263 } else if (SC_CP_UTF8
== dbcsCodePage
) {
264 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
268 if (ch
>= (0x80 + 0x40 + 0x20 + 0x10))
270 else if (ch
>= (0x80 + 0x40 + 0x20))
272 int lengthDoc
= Length();
273 if ((pos
+ len
) > lengthDoc
)
274 return lengthDoc
-pos
;
277 } else if (dbcsCodePage
) {
278 char mbstr
[maxBytesInDBCSCharacter
+1];
280 for (i
=0; i
<Platform::DBCSCharMaxLength(); i
++) {
281 mbstr
[i
] = cb
.CharAt(pos
+i
);
284 return Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
290 static bool IsTrailByte(int ch
) {
291 return (ch
>= 0x80) && (ch
< (0x80 + 0x40));
294 static int BytesFromLead(int leadByte
) {
295 if (leadByte
> 0xF4) {
296 // Characters longer than 4 bytes not possible in current UTF-8
298 } else if (leadByte
>= 0xF0) {
300 } else if (leadByte
>= 0xE0) {
302 } else if (leadByte
>= 0xC2) {
308 bool Document::InGoodUTF8(int pos
, int &start
, int &end
) {
310 while ((lead
>0) && (pos
-lead
< 4) && IsTrailByte(static_cast<unsigned char>(cb
.CharAt(lead
-1))))
316 int leadByte
= static_cast<unsigned char>(cb
.CharAt(start
));
317 int bytes
= BytesFromLead(leadByte
);
321 int trailBytes
= bytes
- 1;
322 int len
= pos
- lead
+ 1;
323 if (len
> trailBytes
)
324 // pos too far from lead
326 // Check that there are enough trails for this lead
328 while ((trail
-lead
<trailBytes
) && (trail
< Length())) {
329 if (!IsTrailByte(static_cast<unsigned char>(cb
.CharAt(trail
)))) {
339 // Normalise a position so that it is not halfway through a two byte character.
340 // This can occur in two situations -
341 // When lines are terminated with \r\n pairs which should be treated as one character.
342 // When displaying DBCS text such as Japanese.
343 // If moving, move the position in the indicated direction.
344 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
345 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
346 // If out of range, just return minimum/maximum value.
352 // PLATFORM_ASSERT(pos > 0 && pos < Length());
353 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
360 // Not between CR and LF
363 if (SC_CP_UTF8
== dbcsCodePage
) {
364 unsigned char ch
= static_cast<unsigned char>(cb
.CharAt(pos
));
367 if (IsTrailByte(ch
) && InGoodUTF8(pos
, startUTF
, endUTF
)) {
368 // ch is a trail byte within a UTF-8 character
375 // Anchor DBCS calculations at start of line because start of line can
376 // not be a DBCS trail byte.
377 int posCheck
= LineStart(LineFromPosition(pos
));
378 while (posCheck
< pos
) {
379 char mbstr
[maxBytesInDBCSCharacter
+1];
381 for(i
=0;i
<Platform::DBCSCharMaxLength();i
++) {
382 mbstr
[i
] = cb
.CharAt(posCheck
+i
);
386 int mbsize
= Platform::DBCSCharLength(dbcsCodePage
, mbstr
);
387 if (posCheck
+ mbsize
== pos
) {
389 } else if (posCheck
+ mbsize
> pos
) {
391 return posCheck
+ mbsize
;
404 void Document::ModifiedAt(int pos
) {
409 void Document::CheckReadOnly() {
410 if (cb
.IsReadOnly() && enteredReadOnlyCount
== 0) {
411 enteredReadOnlyCount
++;
412 NotifyModifyAttempt();
413 enteredReadOnlyCount
--;
417 // Document only modified by gateways DeleteChars, InsertString, Undo, Redo, and SetStyleAt.
418 // SetStyleAt does not change the persistent state of a document
420 bool Document::DeleteChars(int pos
, int len
) {
423 if ((pos
+ len
) > Length())
426 if (enteredModification
!= 0) {
429 enteredModification
++;
430 if (!cb
.IsReadOnly()) {
433 SC_MOD_BEFOREDELETE
| SC_PERFORMED_USER
,
436 int prevLinesTotal
= LinesTotal();
437 bool startSavePoint
= cb
.IsSavePoint();
438 bool startSequence
= false;
439 const char *text
= cb
.DeleteChars(pos
, len
, startSequence
);
440 if (startSavePoint
&& cb
.IsCollectingUndo())
441 NotifySavePoint(!startSavePoint
);
442 if ((pos
< Length()) || (pos
== 0))
448 SC_MOD_DELETETEXT
| SC_PERFORMED_USER
| (startSequence
?SC_STARTACTION
:0),
450 LinesTotal() - prevLinesTotal
, text
));
452 enteredModification
--;
454 return !cb
.IsReadOnly();
458 * Insert a string with a length.
460 bool Document::InsertString(int position
, const char *s
, int insertLength
) {
461 if (insertLength
<= 0) {
465 if (enteredModification
!= 0) {
468 enteredModification
++;
469 if (!cb
.IsReadOnly()) {
472 SC_MOD_BEFOREINSERT
| SC_PERFORMED_USER
,
473 position
, insertLength
,
475 int prevLinesTotal
= LinesTotal();
476 bool startSavePoint
= cb
.IsSavePoint();
477 bool startSequence
= false;
478 const char *text
= cb
.InsertString(position
, s
, insertLength
, startSequence
);
479 if (startSavePoint
&& cb
.IsCollectingUndo())
480 NotifySavePoint(!startSavePoint
);
481 ModifiedAt(position
);
484 SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
| (startSequence
?SC_STARTACTION
:0),
485 position
, insertLength
,
486 LinesTotal() - prevLinesTotal
, text
));
488 enteredModification
--;
490 return !cb
.IsReadOnly();
493 int Document::Undo() {
496 if (enteredModification
== 0) {
497 enteredModification
++;
498 if (!cb
.IsReadOnly()) {
499 bool startSavePoint
= cb
.IsSavePoint();
500 bool multiLine
= false;
501 int steps
= cb
.StartUndo();
502 //Platform::DebugPrintf("Steps=%d\n", steps);
503 for (int step
= 0; step
< steps
; step
++) {
504 const int prevLinesTotal
= LinesTotal();
505 const Action
&action
= cb
.GetUndoStep();
506 if (action
.at
== removeAction
) {
507 NotifyModified(DocModification(
508 SC_MOD_BEFOREINSERT
| SC_PERFORMED_UNDO
, action
));
510 NotifyModified(DocModification(
511 SC_MOD_BEFOREDELETE
| SC_PERFORMED_UNDO
, action
));
513 cb
.PerformUndoStep();
514 int cellPosition
= action
.position
;
515 ModifiedAt(cellPosition
);
516 newPos
= cellPosition
;
518 int modFlags
= SC_PERFORMED_UNDO
;
519 // With undo, an insertion action becomes a deletion notification
520 if (action
.at
== removeAction
) {
521 newPos
+= action
.lenData
;
522 modFlags
|= SC_MOD_INSERTTEXT
;
524 modFlags
|= SC_MOD_DELETETEXT
;
527 modFlags
|= SC_MULTISTEPUNDOREDO
;
528 const int linesAdded
= LinesTotal() - prevLinesTotal
;
531 if (step
== steps
- 1) {
532 modFlags
|= SC_LASTSTEPINUNDOREDO
;
534 modFlags
|= SC_MULTILINEUNDOREDO
;
536 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
537 linesAdded
, action
.data
));
540 bool endSavePoint
= cb
.IsSavePoint();
541 if (startSavePoint
!= endSavePoint
)
542 NotifySavePoint(endSavePoint
);
544 enteredModification
--;
549 int Document::Redo() {
552 if (enteredModification
== 0) {
553 enteredModification
++;
554 if (!cb
.IsReadOnly()) {
555 bool startSavePoint
= cb
.IsSavePoint();
556 bool multiLine
= false;
557 int steps
= cb
.StartRedo();
558 for (int step
= 0; step
< steps
; step
++) {
559 const int prevLinesTotal
= LinesTotal();
560 const Action
&action
= cb
.GetRedoStep();
561 if (action
.at
== insertAction
) {
562 NotifyModified(DocModification(
563 SC_MOD_BEFOREINSERT
| SC_PERFORMED_REDO
, action
));
565 NotifyModified(DocModification(
566 SC_MOD_BEFOREDELETE
| SC_PERFORMED_REDO
, action
));
568 cb
.PerformRedoStep();
569 ModifiedAt(action
.position
);
570 newPos
= action
.position
;
572 int modFlags
= SC_PERFORMED_REDO
;
573 if (action
.at
== insertAction
) {
574 newPos
+= action
.lenData
;
575 modFlags
|= SC_MOD_INSERTTEXT
;
577 modFlags
|= SC_MOD_DELETETEXT
;
580 modFlags
|= SC_MULTISTEPUNDOREDO
;
581 const int linesAdded
= LinesTotal() - prevLinesTotal
;
584 if (step
== steps
- 1) {
585 modFlags
|= SC_LASTSTEPINUNDOREDO
;
587 modFlags
|= SC_MULTILINEUNDOREDO
;
590 DocModification(modFlags
, action
.position
, action
.lenData
,
591 linesAdded
, action
.data
));
594 bool endSavePoint
= cb
.IsSavePoint();
595 if (startSavePoint
!= endSavePoint
)
596 NotifySavePoint(endSavePoint
);
598 enteredModification
--;
604 * Insert a single character.
606 bool Document::InsertChar(int pos
, char ch
) {
609 return InsertString(pos
, chs
, 1);
613 * Insert a null terminated string.
615 bool Document::InsertCString(int position
, const char *s
) {
616 return InsertString(position
, s
, strlen(s
));
619 void Document::ChangeChar(int pos
, char ch
) {
624 void Document::DelChar(int pos
) {
625 DeleteChars(pos
, LenChar(pos
));
628 void Document::DelCharBack(int pos
) {
631 } else if (IsCrLf(pos
- 2)) {
632 DeleteChars(pos
- 2, 2);
633 } else if (dbcsCodePage
) {
634 int startChar
= MovePositionOutsideChar(pos
- 1, -1, false);
635 DeleteChars(startChar
, pos
- startChar
);
637 DeleteChars(pos
- 1, 1);
641 static bool isindentchar(char ch
) {
642 return (ch
== ' ') || (ch
== '\t');
645 static int NextTab(int pos
, int tabSize
) {
646 return ((pos
/ tabSize
) + 1) * tabSize
;
649 static void CreateIndentation(char *linebuf
, int length
, int indent
, int tabSize
, bool insertSpaces
) {
650 length
--; // ensure space for \0
652 while ((indent
>= tabSize
) && (length
> 0)) {
658 while ((indent
> 0) && (length
> 0)) {
666 int Document::GetLineIndentation(int line
) {
668 if ((line
>= 0) && (line
< LinesTotal())) {
669 int lineStart
= LineStart(line
);
670 int length
= Length();
671 for (int i
= lineStart
;i
< length
;i
++) {
672 char ch
= cb
.CharAt(i
);
676 indent
= NextTab(indent
, tabInChars
);
684 void Document::SetLineIndentation(int line
, int indent
) {
685 int indentOfLine
= GetLineIndentation(line
);
688 if (indent
!= indentOfLine
) {
690 CreateIndentation(linebuf
, sizeof(linebuf
), indent
, tabInChars
, !useTabs
);
691 int thisLineStart
= LineStart(line
);
692 int indentPos
= GetLineIndentPosition(line
);
694 DeleteChars(thisLineStart
, indentPos
- thisLineStart
);
695 InsertCString(thisLineStart
, linebuf
);
700 int Document::GetLineIndentPosition(int line
) const {
703 int pos
= LineStart(line
);
704 int length
= Length();
705 while ((pos
< length
) && isindentchar(cb
.CharAt(pos
))) {
711 int Document::GetColumn(int pos
) {
713 int line
= LineFromPosition(pos
);
714 if ((line
>= 0) && (line
< LinesTotal())) {
715 for (int i
= LineStart(line
);i
< pos
;) {
716 char ch
= cb
.CharAt(i
);
718 column
= NextTab(column
, tabInChars
);
720 } else if (ch
== '\r') {
722 } else if (ch
== '\n') {
724 } else if (i
>= Length()) {
728 i
= MovePositionOutsideChar(i
+ 1, 1, false);
735 int Document::FindColumn(int line
, int column
) {
736 int position
= LineStart(line
);
737 int columnCurrent
= 0;
738 if ((line
>= 0) && (line
< LinesTotal())) {
739 while ((columnCurrent
< column
) && (position
< Length())) {
740 char ch
= cb
.CharAt(position
);
742 columnCurrent
= NextTab(columnCurrent
, tabInChars
);
744 } else if (ch
== '\r') {
746 } else if (ch
== '\n') {
750 position
= MovePositionOutsideChar(position
+ 1, 1, false);
757 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
758 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
759 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
760 int indentOfLine
= GetLineIndentation(line
);
762 if (LineStart(line
) < LineEnd(line
)) {
763 SetLineIndentation(line
, indentOfLine
+ IndentSize());
766 SetLineIndentation(line
, indentOfLine
- IndentSize());
771 // Convert line endings for a piece of text to a particular mode.
772 // Stop at len or when a NUL is found.
773 // Caller must delete the returned pointer.
774 char *Document::TransformLineEnds(int *pLenOut
, const char *s
, size_t len
, int eolMode
) {
775 char *dest
= new char[2 * len
+ 1];
776 const char *sptr
= s
;
778 for (size_t i
= 0; (i
< len
) && (*sptr
!= '\0'); i
++) {
779 if (*sptr
== '\n' || *sptr
== '\r') {
780 if (eolMode
== SC_EOL_CR
) {
782 } else if (eolMode
== SC_EOL_LF
) {
784 } else { // eolMode == SC_EOL_CRLF
788 if ((*sptr
== '\r') && (i
+1 < len
) && (*(sptr
+1) == '\n')) {
798 *pLenOut
= (dptr
- dest
) - 1;
802 void Document::ConvertLineEnds(int eolModeSet
) {
805 for (int pos
= 0; pos
< Length(); pos
++) {
806 if (cb
.CharAt(pos
) == '\r') {
807 if (cb
.CharAt(pos
+ 1) == '\n') {
809 if (eolModeSet
== SC_EOL_CR
) {
810 DeleteChars(pos
+ 1, 1); // Delete the LF
811 } else if (eolModeSet
== SC_EOL_LF
) {
812 DeleteChars(pos
, 1); // Delete the CR
818 if (eolModeSet
== SC_EOL_CRLF
) {
819 InsertString(pos
+ 1, "\n", 1); // Insert LF
821 } else if (eolModeSet
== SC_EOL_LF
) {
822 InsertString(pos
, "\n", 1); // Insert LF
823 DeleteChars(pos
+ 1, 1); // Delete CR
826 } else if (cb
.CharAt(pos
) == '\n') {
828 if (eolModeSet
== SC_EOL_CRLF
) {
829 InsertString(pos
, "\r", 1); // Insert CR
831 } else if (eolModeSet
== SC_EOL_CR
) {
832 InsertString(pos
, "\r", 1); // Insert CR
833 DeleteChars(pos
+ 1, 1); // Delete LF
841 bool Document::IsWhiteLine(int line
) const {
842 int currentChar
= LineStart(line
);
843 int endLine
= LineEnd(line
);
844 while (currentChar
< endLine
) {
845 if (cb
.CharAt(currentChar
) != ' ' && cb
.CharAt(currentChar
) != '\t') {
853 int Document::ParaUp(int pos
) {
854 int line
= LineFromPosition(pos
);
856 while (line
>= 0 && IsWhiteLine(line
)) { // skip empty lines
859 while (line
>= 0 && !IsWhiteLine(line
)) { // skip non-empty lines
863 return LineStart(line
);
866 int Document::ParaDown(int pos
) {
867 int line
= LineFromPosition(pos
);
868 while (line
< LinesTotal() && !IsWhiteLine(line
)) { // skip non-empty lines
871 while (line
< LinesTotal() && IsWhiteLine(line
)) { // skip empty lines
874 if (line
< LinesTotal())
875 return LineStart(line
);
876 else // end of a document
877 return LineEnd(line
-1);
880 CharClassify::cc
Document::WordCharClass(unsigned char ch
) {
881 if ((SC_CP_UTF8
== dbcsCodePage
) && (ch
>= 0x80))
882 return CharClassify::ccWord
;
883 return charClass
.GetClass(ch
);
887 * Used by commmands that want to select whole words.
888 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
890 int Document::ExtendWordSelect(int pos
, int delta
, bool onlyWordCharacters
) {
891 CharClassify::cc ccStart
= CharClassify::ccWord
;
893 if (!onlyWordCharacters
)
894 ccStart
= WordCharClass(cb
.CharAt(pos
-1));
895 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
))
898 if (!onlyWordCharacters
&& pos
< Length())
899 ccStart
= WordCharClass(cb
.CharAt(pos
));
900 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
903 return MovePositionOutsideChar(pos
, delta
);
907 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
909 * This is looking for a transition between character classes although there is also some
910 * additional movement to transit white space.
911 * Used by cursor movement by word commands.
913 int Document::NextWordStart(int pos
, int delta
) {
915 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == CharClassify::ccSpace
))
918 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
-1));
919 while (pos
> 0 && (WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
)) {
924 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
));
925 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == ccStart
))
927 while (pos
< (Length()) && (WordCharClass(cb
.CharAt(pos
)) == CharClassify::ccSpace
))
934 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
936 * This is looking for a transition between character classes although there is also some
937 * additional movement to transit white space.
938 * Used by cursor movement by word commands.
940 int Document::NextWordEnd(int pos
, int delta
) {
943 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
-1));
944 if (ccStart
!= CharClassify::ccSpace
) {
945 while (pos
> 0 && WordCharClass(cb
.CharAt(pos
- 1)) == ccStart
) {
949 while (pos
> 0 && WordCharClass(cb
.CharAt(pos
- 1)) == CharClassify::ccSpace
) {
954 while (pos
< Length() && WordCharClass(cb
.CharAt(pos
)) == CharClassify::ccSpace
) {
957 if (pos
< Length()) {
958 CharClassify::cc ccStart
= WordCharClass(cb
.CharAt(pos
));
959 while (pos
< Length() && WordCharClass(cb
.CharAt(pos
)) == ccStart
) {
968 * Check that the character at the given position is a word or punctuation character and that
969 * the previous character is of a different character class.
971 bool Document::IsWordStartAt(int pos
) {
973 CharClassify::cc ccPos
= WordCharClass(CharAt(pos
));
974 return (ccPos
== CharClassify::ccWord
|| ccPos
== CharClassify::ccPunctuation
) &&
975 (ccPos
!= WordCharClass(CharAt(pos
- 1)));
981 * Check that the character at the given position is a word or punctuation character and that
982 * the next character is of a different character class.
984 bool Document::IsWordEndAt(int pos
) {
985 if (pos
< Length()) {
986 CharClassify::cc ccPrev
= WordCharClass(CharAt(pos
-1));
987 return (ccPrev
== CharClassify::ccWord
|| ccPrev
== CharClassify::ccPunctuation
) &&
988 (ccPrev
!= WordCharClass(CharAt(pos
)));
994 * Check that the given range is has transitions between character classes at both
995 * ends and where the characters on the inside are word or punctuation characters.
997 bool Document::IsWordAt(int start
, int end
) {
998 return IsWordStartAt(start
) && IsWordEndAt(end
);
1001 // The comparison and case changing functions here assume ASCII
1002 // or extended ASCII such as the normal Windows code page.
1004 static inline char MakeUpperCase(char ch
) {
1005 if (ch
< 'a' || ch
> 'z')
1008 return static_cast<char>(ch
- 'a' + 'A');
1011 static inline char MakeLowerCase(char ch
) {
1012 if (ch
< 'A' || ch
> 'Z')
1015 return static_cast<char>(ch
- 'A' + 'a');
1018 // Define a way for the Regular Expression code to access the document
1019 class DocumentIndexer
: public CharacterIndexer
{
1023 DocumentIndexer(Document
*pdoc_
, int end_
) :
1024 pdoc(pdoc_
), end(end_
) {
1027 virtual ~DocumentIndexer() {
1030 virtual char CharAt(int index
) {
1031 if (index
< 0 || index
>= end
)
1034 return pdoc
->CharAt(index
);
1039 * Find text in document, supporting both forward and backward
1040 * searches (just pass minPos > maxPos to do a backward search)
1041 * Has not been tested with backwards DBCS searches yet.
1043 long Document::FindText(int minPos
, int maxPos
, const char *s
,
1044 bool caseSensitive
, bool word
, bool wordStart
, bool regExp
, bool posix
,
1048 pre
= new RESearch(&charClass
);
1052 int increment
= (minPos
<= maxPos
) ? 1 : -1;
1054 int startPos
= minPos
;
1055 int endPos
= maxPos
;
1057 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1058 startPos
= MovePositionOutsideChar(startPos
, 1, false);
1059 endPos
= MovePositionOutsideChar(endPos
, 1, false);
1061 const char *errmsg
= pre
->Compile(s
, *length
, caseSensitive
, posix
);
1065 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
1066 // Replace first '.' with '-' in each property file variable reference:
1067 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
1068 // Replace: $(\1-\2)
1069 int lineRangeStart
= LineFromPosition(startPos
);
1070 int lineRangeEnd
= LineFromPosition(endPos
);
1071 if ((increment
== 1) &&
1072 (startPos
>= LineEnd(lineRangeStart
)) &&
1073 (lineRangeStart
< lineRangeEnd
)) {
1074 // the start position is at end of line or between line end characters.
1076 startPos
= LineStart(lineRangeStart
);
1080 char searchEnd
= s
[*length
- 1];
1081 int lineRangeBreak
= lineRangeEnd
+ increment
;
1082 for (int line
= lineRangeStart
; line
!= lineRangeBreak
; line
+= increment
) {
1083 int startOfLine
= LineStart(line
);
1084 int endOfLine
= LineEnd(line
);
1085 if (increment
== 1) {
1086 if (line
== lineRangeStart
) {
1087 if ((startPos
!= startOfLine
) && (s
[0] == '^'))
1088 continue; // Can't match start of line if start position after start of line
1089 startOfLine
= startPos
;
1091 if (line
== lineRangeEnd
) {
1092 if ((endPos
!= endOfLine
) && (searchEnd
== '$'))
1093 continue; // Can't match end of line if end position before end of line
1097 if (line
== lineRangeEnd
) {
1098 if ((endPos
!= startOfLine
) && (s
[0] == '^'))
1099 continue; // Can't match start of line if end position after start of line
1100 startOfLine
= endPos
;
1102 if (line
== lineRangeStart
) {
1103 if ((startPos
!= endOfLine
) && (searchEnd
== '$'))
1104 continue; // Can't match end of line if start position before end of line
1105 endOfLine
= startPos
;
1109 DocumentIndexer
di(this, endOfLine
);
1110 int success
= pre
->Execute(di
, startOfLine
, endOfLine
);
1112 pos
= pre
->bopat
[0];
1113 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
1114 if (increment
== -1) {
1115 // Check for the last match on this line.
1116 int repetitions
= 1000; // Break out of infinite loop
1117 while (success
&& (pre
->eopat
[0] <= endOfLine
) && (repetitions
--)) {
1118 success
= pre
->Execute(di
, pos
+1, endOfLine
);
1120 if (pre
->eopat
[0] <= minPos
) {
1121 pos
= pre
->bopat
[0];
1122 lenRet
= pre
->eopat
[0] - pre
->bopat
[0];
1137 bool forward
= minPos
<= maxPos
;
1138 int increment
= forward
? 1 : -1;
1140 // Range endpoints should not be inside DBCS characters, but just in case, move them.
1141 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
1142 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
1144 // Compute actual search ranges needed
1145 int lengthFind
= *length
;
1146 if (lengthFind
== -1)
1147 lengthFind
= static_cast<int>(strlen(s
));
1148 int endSearch
= endPos
;
1149 if (startPos
<= endPos
) {
1150 endSearch
= endPos
- lengthFind
+ 1;
1152 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
1153 char firstChar
= s
[0];
1155 firstChar
= static_cast<char>(MakeUpperCase(firstChar
));
1156 int pos
= forward
? startPos
: (startPos
- 1);
1157 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
1158 char ch
= CharAt(pos
);
1159 if (caseSensitive
) {
1160 if (ch
== firstChar
) {
1162 if (pos
+ lengthFind
> Platform::Maximum(startPos
, endPos
)) found
= false;
1163 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
1164 ch
= CharAt(pos
+ posMatch
);
1165 if (ch
!= s
[posMatch
])
1169 if ((!word
&& !wordStart
) ||
1170 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
1171 wordStart
&& IsWordStartAt(pos
))
1176 if (MakeUpperCase(ch
) == firstChar
) {
1178 if (pos
+ lengthFind
> Platform::Maximum(startPos
, endPos
)) found
= false;
1179 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
1180 ch
= CharAt(pos
+ posMatch
);
1181 if (MakeUpperCase(ch
) != MakeUpperCase(s
[posMatch
]))
1185 if ((!word
&& !wordStart
) ||
1186 word
&& IsWordAt(pos
, pos
+ lengthFind
) ||
1187 wordStart
&& IsWordStartAt(pos
))
1193 if (dbcsCodePage
&& (pos
>= 0)) {
1194 // Ensure trying to match from start of character
1195 pos
= MovePositionOutsideChar(pos
, increment
, false);
1199 //Platform::DebugPrintf("Not found\n");
1203 const char *Document::SubstituteByPosition(const char *text
, int *length
) {
1206 delete []substituted
;
1208 DocumentIndexer
di(this, Length());
1209 if (!pre
->GrabMatches(di
))
1211 unsigned int lenResult
= 0;
1212 for (int i
= 0; i
< *length
; i
++) {
1213 if (text
[i
] == '\\') {
1214 if (text
[i
+ 1] >= '1' && text
[i
+ 1] <= '9') {
1215 unsigned int patNum
= text
[i
+ 1] - '0';
1216 lenResult
+= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1219 switch (text
[i
+ 1]) {
1235 substituted
= new char[lenResult
+ 1];
1238 char *o
= substituted
;
1239 for (int j
= 0; j
< *length
; j
++) {
1240 if (text
[j
] == '\\') {
1241 if (text
[j
+ 1] >= '1' && text
[j
+ 1] <= '9') {
1242 unsigned int patNum
= text
[j
+ 1] - '0';
1243 unsigned int len
= pre
->eopat
[patNum
] - pre
->bopat
[patNum
];
1244 if (pre
->pat
[patNum
]) // Will be null if try for a match that did not occur
1245 memcpy(o
, pre
->pat
[patNum
], len
);
1282 *length
= lenResult
;
1286 int Document::LinesTotal() const {
1290 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
1291 for (int pos
= r
.start
; pos
< r
.end
;) {
1292 int len
= LenChar(pos
);
1294 char ch
= CharAt(pos
);
1295 if (makeUpperCase
) {
1296 if (IsLowerCase(ch
)) {
1297 ChangeChar(pos
, static_cast<char>(MakeUpperCase(ch
)));
1300 if (IsUpperCase(ch
)) {
1301 ChangeChar(pos
, static_cast<char>(MakeLowerCase(ch
)));
1309 void Document::SetDefaultCharClasses(bool includeWordClass
) {
1310 charClass
.SetDefaultCharClasses(includeWordClass
);
1313 void Document::SetCharClasses(const unsigned char *chars
, CharClassify::cc newCharClass
) {
1314 charClass
.SetCharClasses(chars
, newCharClass
);
1317 void Document::SetStylingBits(int bits
) {
1319 stylingBitsMask
= (1 << stylingBits
) - 1;
1322 void Document::StartStyling(int position
, char mask
) {
1324 endStyled
= position
;
1327 bool Document::SetStyleFor(int length
, char style
) {
1328 if (enteredStyling
!= 0) {
1332 style
&= stylingMask
;
1333 int prevEndStyled
= endStyled
;
1334 if (cb
.SetStyleFor(endStyled
, length
, style
, stylingMask
)) {
1335 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1336 prevEndStyled
, length
);
1339 endStyled
+= length
;
1345 bool Document::SetStyles(int length
, char *styles
) {
1346 if (enteredStyling
!= 0) {
1350 bool didChange
= false;
1353 for (int iPos
= 0; iPos
< length
; iPos
++, endStyled
++) {
1354 PLATFORM_ASSERT(endStyled
< Length());
1355 if (cb
.SetStyleAt(endStyled
, styles
[iPos
], stylingMask
)) {
1357 startMod
= endStyled
;
1364 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
1365 startMod
, endMod
- startMod
+ 1);
1373 void Document::EnsureStyledTo(int pos
) {
1374 if ((enteredStyling
== 0) && (pos
> GetEndStyled())) {
1375 IncrementStyleClock();
1376 // Ask the watchers to style, and stop as soon as one responds.
1377 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++) {
1378 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
1383 int Document::SetLineState(int line
, int state
) {
1384 int statePrevious
= cb
.SetLineState(line
, state
);
1385 if (state
!= statePrevious
) {
1386 DocModification
mh(SC_MOD_CHANGELINESTATE
, 0, 0, 0, 0, line
);
1389 return statePrevious
;
1392 void Document::IncrementStyleClock() {
1393 styleClock
= (styleClock
+ 1) % 0x100000;
1396 void Document::DecorationFillRange(int position
, int value
, int fillLength
) {
1397 if (decorations
.FillRange(position
, value
, fillLength
)) {
1398 DocModification
mh(SC_MOD_CHANGEINDICATOR
| SC_PERFORMED_USER
,
1399 position
, fillLength
);
1404 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
1405 for (int i
= 0; i
< lenWatchers
; i
++) {
1406 if ((watchers
[i
].watcher
== watcher
) &&
1407 (watchers
[i
].userData
== userData
))
1410 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
1413 for (int j
= 0; j
< lenWatchers
; j
++)
1414 pwNew
[j
] = watchers
[j
];
1415 pwNew
[lenWatchers
].watcher
= watcher
;
1416 pwNew
[lenWatchers
].userData
= userData
;
1423 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
1424 for (int i
= 0; i
< lenWatchers
; i
++) {
1425 if ((watchers
[i
].watcher
== watcher
) &&
1426 (watchers
[i
].userData
== userData
)) {
1427 if (lenWatchers
== 1) {
1432 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
1435 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
1436 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
1448 void Document::NotifyModifyAttempt() {
1449 for (int i
= 0; i
< lenWatchers
; i
++) {
1450 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
1454 void Document::NotifySavePoint(bool atSavePoint
) {
1455 for (int i
= 0; i
< lenWatchers
; i
++) {
1456 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
1460 void Document::NotifyModified(DocModification mh
) {
1461 if (mh
.modificationType
& SC_MOD_INSERTTEXT
) {
1462 decorations
.InsertSpace(mh
.position
, mh
.length
);
1463 } else if (mh
.modificationType
& SC_MOD_DELETETEXT
) {
1464 decorations
.DeleteRange(mh
.position
, mh
.length
);
1466 for (int i
= 0; i
< lenWatchers
; i
++) {
1467 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);
1471 bool Document::IsWordPartSeparator(char ch
) {
1472 return (WordCharClass(ch
) == CharClassify::ccWord
) && IsPunctuation(ch
);
1475 int Document::WordPartLeft(int pos
) {
1478 char startChar
= cb
.CharAt(pos
);
1479 if (IsWordPartSeparator(startChar
)) {
1480 while (pos
> 0 && IsWordPartSeparator(cb
.CharAt(pos
))) {
1485 startChar
= cb
.CharAt(pos
);
1487 if (IsLowerCase(startChar
)) {
1488 while (pos
> 0 && IsLowerCase(cb
.CharAt(pos
)))
1490 if (!IsUpperCase(cb
.CharAt(pos
)) && !IsLowerCase(cb
.CharAt(pos
)))
1492 } else if (IsUpperCase(startChar
)) {
1493 while (pos
> 0 && IsUpperCase(cb
.CharAt(pos
)))
1495 if (!IsUpperCase(cb
.CharAt(pos
)))
1497 } else if (IsADigit(startChar
)) {
1498 while (pos
> 0 && IsADigit(cb
.CharAt(pos
)))
1500 if (!IsADigit(cb
.CharAt(pos
)))
1502 } else if (IsPunctuation(startChar
)) {
1503 while (pos
> 0 && IsPunctuation(cb
.CharAt(pos
)))
1505 if (!IsPunctuation(cb
.CharAt(pos
)))
1507 } else if (isspacechar(startChar
)) {
1508 while (pos
> 0 && isspacechar(cb
.CharAt(pos
)))
1510 if (!isspacechar(cb
.CharAt(pos
)))
1512 } else if (!isascii(startChar
)) {
1513 while (pos
> 0 && !isascii(cb
.CharAt(pos
)))
1515 if (isascii(cb
.CharAt(pos
)))
1525 int Document::WordPartRight(int pos
) {
1526 char startChar
= cb
.CharAt(pos
);
1527 int length
= Length();
1528 if (IsWordPartSeparator(startChar
)) {
1529 while (pos
< length
&& IsWordPartSeparator(cb
.CharAt(pos
)))
1531 startChar
= cb
.CharAt(pos
);
1533 if (!isascii(startChar
)) {
1534 while (pos
< length
&& !isascii(cb
.CharAt(pos
)))
1536 } else if (IsLowerCase(startChar
)) {
1537 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1539 } else if (IsUpperCase(startChar
)) {
1540 if (IsLowerCase(cb
.CharAt(pos
+ 1))) {
1542 while (pos
< length
&& IsLowerCase(cb
.CharAt(pos
)))
1545 while (pos
< length
&& IsUpperCase(cb
.CharAt(pos
)))
1548 if (IsLowerCase(cb
.CharAt(pos
)) && IsUpperCase(cb
.CharAt(pos
- 1)))
1550 } else if (IsADigit(startChar
)) {
1551 while (pos
< length
&& IsADigit(cb
.CharAt(pos
)))
1553 } else if (IsPunctuation(startChar
)) {
1554 while (pos
< length
&& IsPunctuation(cb
.CharAt(pos
)))
1556 } else if (isspacechar(startChar
)) {
1557 while (pos
< length
&& isspacechar(cb
.CharAt(pos
)))
1565 bool IsLineEndChar(char c
) {
1566 return (c
== '\n' || c
== '\r');
1569 int Document::ExtendStyleRange(int pos
, int delta
, bool singleLine
) {
1570 int sStart
= cb
.StyleAt(pos
);
1572 while (pos
> 0 && (cb
.StyleAt(pos
) == sStart
) && (!singleLine
|| !IsLineEndChar(cb
.CharAt(pos
))) )
1576 while (pos
< (Length()) && (cb
.StyleAt(pos
) == sStart
) && (!singleLine
|| !IsLineEndChar(cb
.CharAt(pos
))) )
1582 static char BraceOpposite(char ch
) {
1605 // TODO: should be able to extend styled region to find matching brace
1606 int Document::BraceMatch(int position
, int /*maxReStyle*/) {
1607 char chBrace
= CharAt(position
);
1608 char chSeek
= BraceOpposite(chBrace
);
1611 char styBrace
= static_cast<char>(StyleAt(position
) & stylingBitsMask
);
1613 if (chBrace
== '(' || chBrace
== '[' || chBrace
== '{' || chBrace
== '<')
1616 position
= position
+ direction
;
1617 while ((position
>= 0) && (position
< Length())) {
1618 position
= MovePositionOutsideChar(position
, direction
);
1619 char chAtPos
= CharAt(position
);
1620 char styAtPos
= static_cast<char>(StyleAt(position
) & stylingBitsMask
);
1621 if ((position
> GetEndStyled()) || (styAtPos
== styBrace
)) {
1622 if (chAtPos
== chBrace
)
1624 if (chAtPos
== chSeek
)
1629 position
= position
+ direction
;