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;