r5079
[geany-mirror.git] / scintilla / Document.cxx
blob3c90d1f425415547cddbcce643827bdb00ce2dc8
1 // Scintilla source code edit control
2 /** @file Document.cxx
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
4 **/
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.
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <ctype.h>
13 #include <string>
14 #include <vector>
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.
18 #ifdef __BORLANDC__
19 #ifdef FindText
20 #undef FindText
21 #endif
22 #endif
24 #include "Platform.h"
26 #include "Scintilla.h"
27 #include "SplitVector.h"
28 #include "Partitioning.h"
29 #include "RunStyles.h"
30 #include "CellBuffer.h"
31 #include "PerLine.h"
32 #include "CharClassify.h"
33 #include "Decoration.h"
34 #include "Document.h"
35 #include "RESearch.h"
36 #include "UniConversion.h"
38 #ifdef SCI_NAMESPACE
39 using namespace Scintilla;
40 #endif
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() {
64 refCount = 0;
65 #ifdef unix
66 eolMode = SC_EOL_LF;
67 #else
68 eolMode = SC_EOL_CRLF;
69 #endif
70 dbcsCodePage = 0;
71 stylingBits = 5;
72 stylingBitsMask = 0x1F;
73 stylingMask = 0;
74 endStyled = 0;
75 styleClock = 0;
76 enteredModification = 0;
77 enteredStyling = 0;
78 enteredReadOnlyCount = 0;
79 tabInChars = 8;
80 indentInChars = 0;
81 actualIndentInChars = 8;
82 useTabs = true;
83 tabIndents = true;
84 backspaceUnindents = false;
85 watchers = 0;
86 lenWatchers = 0;
88 matchesValid = false;
89 regex = 0;
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();
97 cb.SetPerLine(this);
100 Document::~Document() {
101 for (int i = 0; i < lenWatchers; i++) {
102 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
104 delete []watchers;
105 for (int j=0; j<ldSize; j++) {
106 delete perLineData[j];
107 perLineData[j] = 0;
109 watchers = 0;
110 lenWatchers = 0;
111 delete regex;
112 regex = 0;
115 void Document::Init() {
116 for (int j=0; j<ldSize; j++) {
117 if (perLineData[j])
118 perLineData[j]->Init();
122 void Document::InsertLine(int line) {
123 for (int j=0; j<ldSize; j++) {
124 if (perLineData[j])
125 perLineData[j]->InsertLine(line);
129 void Document::RemoveLine(int line) {
130 for (int j=0; j<ldSize; j++) {
131 if (perLineData[j])
132 perLineData[j]->RemoveLine(line);
136 // Increase reference count and return its previous value.
137 int Document::AddRef() {
138 return refCount++;
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)
146 delete this;
147 return curRefCount;
150 void Document::SetSavePoint() {
151 cb.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);
164 NotifyModified(mh);
165 return prev;
166 } else {
167 return 0;
171 void Document::AddMarkSet(int line, int valueSet) {
172 unsigned int m = valueSet;
173 for (int i = 0; m; i++, m >>= 1)
174 if (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);
178 NotifyModified(mh);
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);
184 NotifyModified(mh);
187 void Document::DeleteMarkFromHandle(int markerHandle) {
188 static_cast<LineMarkers *>(perLineData[ldMarkers])->DeleteMarkFromHandle(markerHandle);
189 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
190 mh.line = -1;
191 NotifyModified(mh);
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);
199 mh.line = -1;
200 NotifyModified(mh);
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);
214 } else {
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')) {
218 position--;
220 return position;
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'))
242 startText++;
243 if (position == startText)
244 return startPosition;
245 else
246 return startText;
249 int Document::SetLevel(int line, int level) {
250 int prev = static_cast<LineLevels *>(perLineData[ldLevels])->SetLevel(line, level, LinesTotal());
251 if (prev != level) {
252 DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
253 LineStart(line), 0, 0, 0, line);
254 mh.foldLevelNow = level;
255 mh.foldLevelPrev = prev;
256 NotifyModified(mh);
258 return 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)
271 return true;
272 else
273 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
276 int Document::GetLastChild(int lineParent, int level) {
277 if (level == -1)
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)))
284 break;
285 lineMaxSubord++;
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) {
291 lineMaxSubord--;
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))
305 lineLook--;
307 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
308 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
309 return lineLook;
310 } else {
311 return -1;
315 int Document::ClampPositionIntoDocument(int pos) {
316 return Platform::Clamp(pos, 0, Length());
319 bool Document::IsCrLf(int pos) {
320 if (pos < 0)
321 return false;
322 if (pos >= (Length() - 1))
323 return false;
324 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
327 static const int maxBytesInDBCSCharacter=5;
329 int Document::LenChar(int pos) {
330 if (pos < 0) {
331 return 1;
332 } else if (IsCrLf(pos)) {
333 return 2;
334 } else if (SC_CP_UTF8 == dbcsCodePage) {
335 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
336 if (ch < 0x80)
337 return 1;
338 int len = 2;
339 if (ch >= (0x80 + 0x40 + 0x20 + 0x10))
340 len = 4;
341 else if (ch >= (0x80 + 0x40 + 0x20))
342 len = 3;
343 int lengthDoc = Length();
344 if ((pos + len) > lengthDoc)
345 return lengthDoc -pos;
346 else
347 return len;
348 } else if (dbcsCodePage) {
349 char mbstr[maxBytesInDBCSCharacter+1];
350 int i;
351 for (i=0; i<Platform::DBCSCharMaxLength(); i++) {
352 mbstr[i] = cb.CharAt(pos+i);
354 mbstr[i] = '\0';
355 return Platform::DBCSCharLength(dbcsCodePage, mbstr);
356 } else {
357 return 1;
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
368 return 0;
369 } else if (leadByte >= 0xF0) {
370 return 4;
371 } else if (leadByte >= 0xE0) {
372 return 3;
373 } else if (leadByte >= 0xC2) {
374 return 2;
376 return 0;
379 bool Document::InGoodUTF8(int pos, int &start, int &end) {
380 int lead = pos;
381 while ((lead>0) && (pos-lead < 4) && IsTrailByte(static_cast<unsigned char>(cb.CharAt(lead-1))))
382 lead--;
383 start = 0;
384 if (lead > 0) {
385 start = lead-1;
387 int leadByte = static_cast<unsigned char>(cb.CharAt(start));
388 int bytes = BytesFromLead(leadByte);
389 if (bytes == 0) {
390 return false;
391 } else {
392 int trailBytes = bytes - 1;
393 int len = pos - lead + 1;
394 if (len > trailBytes)
395 // pos too far from lead
396 return false;
397 // Check that there are enough trails for this lead
398 int trail = pos + 1;
399 while ((trail-lead<trailBytes) && (trail < Length())) {
400 if (!IsTrailByte(static_cast<unsigned char>(cb.CharAt(trail)))) {
401 return false;
403 trail++;
405 end = start + bytes;
406 return true;
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.
418 if (pos <= 0)
419 return 0;
420 if (pos >= Length())
421 return Length();
423 // PLATFORM_ASSERT(pos > 0 && pos < Length());
424 if (checkLineEnd && IsCrLf(pos - 1)) {
425 if (moveDir > 0)
426 return pos + 1;
427 else
428 return pos - 1;
431 // Not between CR and LF
433 if (dbcsCodePage) {
434 if (SC_CP_UTF8 == dbcsCodePage) {
435 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
436 int startUTF = pos;
437 int endUTF = pos;
438 if (IsTrailByte(ch) && InGoodUTF8(pos, startUTF, endUTF)) {
439 // ch is a trail byte within a UTF-8 character
440 if (moveDir > 0)
441 pos = endUTF;
442 else
443 pos = startUTF;
445 } else {
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];
451 int i;
452 for (i=0; i<Platform::DBCSCharMaxLength(); i++) {
453 mbstr[i] = cb.CharAt(posCheck+i);
455 mbstr[i] = '\0';
457 int mbsize = Platform::DBCSCharLength(dbcsCodePage, mbstr);
458 if (posCheck + mbsize == pos) {
459 return pos;
460 } else if (posCheck + mbsize > pos) {
461 if (moveDir > 0) {
462 return posCheck + mbsize;
463 } else {
464 return posCheck;
467 posCheck += mbsize;
472 return pos;
475 void Document::ModifiedAt(int pos) {
476 if (endStyled > pos)
477 endStyled = 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) {
492 if (len == 0)
493 return false;
494 if ((pos + len) > Length())
495 return false;
496 CheckReadOnly();
497 if (enteredModification != 0) {
498 return false;
499 } else {
500 enteredModification++;
501 if (!cb.IsReadOnly()) {
502 NotifyModified(
503 DocModification(
504 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
505 pos, len,
506 0, 0));
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))
514 ModifiedAt(pos);
515 else
516 ModifiedAt(pos-1);
517 NotifyModified(
518 DocModification(
519 SC_MOD_DELETETEXT | SC_PERFORMED_USER | (startSequence?SC_STARTACTION:0),
520 pos, len,
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) {
533 return false;
535 CheckReadOnly();
536 if (enteredModification != 0) {
537 return false;
538 } else {
539 enteredModification++;
540 if (!cb.IsReadOnly()) {
541 NotifyModified(
542 DocModification(
543 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
544 position, insertLength,
545 0, s));
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);
553 NotifyModified(
554 DocModification(
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() {
565 int newPos = -1;
566 CheckReadOnly();
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;
583 NotifyModified(dm);
584 } else {
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;
603 if (steps > 1)
604 modFlags |= SC_MULTISTEPUNDOREDO;
605 const int linesAdded = LinesTotal() - prevLinesTotal;
606 if (linesAdded != 0)
607 multiLine = true;
608 if (step == steps - 1) {
609 modFlags |= SC_LASTSTEPINUNDOREDO;
610 if (multiLine)
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--;
623 return newPos;
626 int Document::Redo() {
627 int newPos = -1;
628 CheckReadOnly();
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;
644 NotifyModified(dm);
645 } else {
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;
662 if (steps > 1)
663 modFlags |= SC_MULTISTEPUNDOREDO;
664 const int linesAdded = LinesTotal() - prevLinesTotal;
665 if (linesAdded != 0)
666 multiLine = true;
667 if (step == steps - 1) {
668 modFlags |= SC_LASTSTEPINUNDOREDO;
669 if (multiLine)
670 modFlags |= SC_MULTILINEUNDOREDO;
672 NotifyModified(
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--;
683 return newPos;
687 * Insert a single character.
689 bool Document::InsertChar(int pos, char ch) {
690 char chs[1];
691 chs[0] = 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) {
703 DeleteChars(pos, 1);
704 InsertChar(pos, ch);
707 void Document::DelChar(int pos) {
708 DeleteChars(pos, LenChar(pos));
711 void Document::DelCharBack(int pos) {
712 if (pos <= 0) {
713 return;
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);
719 } else {
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
734 if (!insertSpaces) {
735 while ((indent >= tabSize) && (length > 0)) {
736 *linebuf++ = '\t';
737 indent -= tabSize;
738 length--;
741 while ((indent > 0) && (length > 0)) {
742 *linebuf++ = ' ';
743 indent--;
744 length--;
746 *linebuf = '\0';
749 int Document::GetLineIndentation(int line) {
750 int indent = 0;
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);
756 if (ch == ' ')
757 indent++;
758 else if (ch == '\t')
759 indent = NextTab(indent, tabInChars);
760 else
761 return indent;
764 return indent;
767 void Document::SetLineIndentation(int line, int indent) {
768 int indentOfLine = GetLineIndentation(line);
769 if (indent < 0)
770 indent = 0;
771 if (indent != indentOfLine) {
772 char linebuf[1000];
773 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
774 int thisLineStart = LineStart(line);
775 int indentPos = GetLineIndentPosition(line);
776 UndoGroup ug(this);
777 DeleteChars(thisLineStart, indentPos - thisLineStart);
778 InsertCString(thisLineStart, linebuf);
782 int Document::GetLineIndentPosition(int line) const {
783 if (line < 0)
784 return 0;
785 int pos = LineStart(line);
786 int length = Length();
787 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
788 pos++;
790 return pos;
793 int Document::GetColumn(int pos) {
794 int column = 0;
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);
799 if (ch == '\t') {
800 column = NextTab(column, tabInChars);
801 i++;
802 } else if (ch == '\r') {
803 return column;
804 } else if (ch == '\n') {
805 return column;
806 } else if (i >= Length()) {
807 return column;
808 } else {
809 column++;
810 i = MovePositionOutsideChar(i + 1, 1, false);
814 return column;
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);
823 if (ch == '\t') {
824 columnCurrent = NextTab(columnCurrent, tabInChars);
825 position++;
826 } else if (ch == '\r') {
827 return position;
828 } else if (ch == '\n') {
829 return position;
830 } else {
831 columnCurrent++;
832 position = MovePositionOutsideChar(position + 1, 1, false);
836 return position;
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);
843 if (forwards) {
844 if (LineStart(line) < LineEnd(line)) {
845 SetLineIndentation(line, indentOfLine + IndentSize());
847 } else {
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;
859 char *dptr = dest;
860 for (size_t i = 0; (i < len) && (*sptr != '\0'); i++) {
861 if (*sptr == '\n' || *sptr == '\r') {
862 if (eolMode == SC_EOL_CR) {
863 *dptr++ = '\r';
864 } else if (eolMode == SC_EOL_LF) {
865 *dptr++ = '\n';
866 } else { // eolMode == SC_EOL_CRLF
867 *dptr++ = '\r';
868 *dptr++ = '\n';
870 if ((*sptr == '\r') && (i+1 < len) && (*(sptr+1) == '\n')) {
871 i++;
872 sptr++;
874 sptr++;
875 } else {
876 *dptr++ = *sptr++;
879 *dptr++ = '\0';
880 *pLenOut = (dptr - dest) - 1;
881 return dest;
884 void Document::ConvertLineEnds(int eolModeSet) {
885 UndoGroup ug(this);
887 for (int pos = 0; pos < Length(); pos++) {
888 if (cb.CharAt(pos) == '\r') {
889 if (cb.CharAt(pos + 1) == '\n') {
890 // CRLF
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
895 } else {
896 pos++;
898 } else {
899 // CR
900 if (eolModeSet == SC_EOL_CRLF) {
901 InsertString(pos + 1, "\n", 1); // Insert LF
902 pos++;
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') {
909 // LF
910 if (eolModeSet == SC_EOL_CRLF) {
911 InsertString(pos, "\r", 1); // Insert CR
912 pos++;
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') {
927 return false;
929 ++currentChar;
931 return true;
934 int Document::ParaUp(int pos) {
935 int line = LineFromPosition(pos);
936 line--;
937 while (line >= 0 && IsWhiteLine(line)) { // skip empty lines
938 line--;
940 while (line >= 0 && !IsWhiteLine(line)) { // skip non-empty lines
941 line--;
943 line++;
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
950 line++;
952 while (line < LinesTotal() && IsWhiteLine(line)) { // skip empty lines
953 line++;
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;
973 if (delta < 0) {
974 if (!onlyWordCharacters)
975 ccStart = WordCharClass(cb.CharAt(pos-1));
976 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart))
977 pos--;
978 } else {
979 if (!onlyWordCharacters && pos < Length())
980 ccStart = WordCharClass(cb.CharAt(pos));
981 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
982 pos++;
984 return MovePositionOutsideChar(pos, delta, true);
988 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
989 * (delta < 0).
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) {
995 if (delta < 0) {
996 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace))
997 pos--;
998 if (pos > 0) {
999 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));
1000 while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) {
1001 pos--;
1004 } else {
1005 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));
1006 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
1007 pos++;
1008 while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace))
1009 pos++;
1011 return pos;
1015 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
1016 * (delta < 0).
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) {
1022 if (delta < 0) {
1023 if (pos > 0) {
1024 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));
1025 if (ccStart != CharClassify::ccSpace) {
1026 while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) {
1027 pos--;
1030 while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace) {
1031 pos--;
1034 } else {
1035 while (pos < Length() && WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace) {
1036 pos++;
1038 if (pos < Length()) {
1039 CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));
1040 while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) {
1041 pos++;
1045 return pos;
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) {
1053 if (pos > 0) {
1054 CharClassify::cc ccPos = WordCharClass(CharAt(pos));
1055 return (ccPos == CharClassify::ccWord || ccPos == CharClassify::ccPunctuation) &&
1056 (ccPos != WordCharClass(CharAt(pos - 1)));
1058 return true;
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)));
1071 return true;
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')
1084 return ch;
1085 else
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);
1096 bytes[0] = 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
1100 widthChar = 1;
1103 return widthChar;
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) {
1117 return 0;
1118 } else {
1119 for (size_t i=0; i<lenMixed; i++) {
1120 folded[i] = mapping[static_cast<unsigned char>(mixed[i])];
1122 return lenMixed;
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');
1134 } else {
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) {
1154 if (regExp) {
1155 if (!regex)
1156 regex = CreateRegexSearch(&charClass);
1157 return regex->FindText(this, minPos, maxPos, search, caseSensitive, word, wordStart, flags, length);
1158 } else {
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)) {
1181 return pos;
1183 pos += increment;
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;
1220 return pos;
1223 if (forward) {
1224 pos += widthFirstCharacter;
1225 } else {
1226 pos--;
1227 if (pos > 0) {
1228 // Ensure trying to match from start of character
1229 pos = MovePositionOutsideChar(pos, increment, false);
1233 } else {
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);
1241 char folded[2];
1242 pcf->Fold(folded, sizeof(folded), &ch, 1);
1243 found = folded[0] == searchThing[indexSearch];
1245 if (found && MatchesWordOptions(word, wordStart, pos, lengthFind)) {
1246 return pos;
1248 pos += increment;
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");
1257 return -1;
1260 const char *Document::SubstituteByPosition(const char *text, int *length) {
1261 return regex->SubstituteByPosition(this, text, length);
1264 int Document::LinesTotal() const {
1265 return cb.Lines();
1268 void Document::ChangeCase(Range r, bool makeUpperCase) {
1269 for (int pos = r.start; pos < r.end;) {
1270 int len = LenChar(pos);
1271 if (len == 1) {
1272 char ch = CharAt(pos);
1273 if (makeUpperCase) {
1274 if (IsLowerCase(ch)) {
1275 ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
1277 } else {
1278 if (IsUpperCase(ch)) {
1279 ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
1283 pos += len;
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) {
1296 stylingBits = bits;
1297 stylingBitsMask = (1 << stylingBits) - 1;
1300 void Document::StartStyling(int position, char mask) {
1301 stylingMask = mask;
1302 endStyled = position;
1305 bool Document::SetStyleFor(int length, char style) {
1306 if (enteredStyling != 0) {
1307 return false;
1308 } else {
1309 enteredStyling++;
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);
1315 NotifyModified(mh);
1317 endStyled += length;
1318 enteredStyling--;
1319 return true;
1323 bool Document::SetStyles(int length, const char *styles) {
1324 if (enteredStyling != 0) {
1325 return false;
1326 } else {
1327 enteredStyling++;
1328 bool didChange = false;
1329 int startMod = 0;
1330 int endMod = 0;
1331 for (int iPos = 0; iPos < length; iPos++, endStyled++) {
1332 PLATFORM_ASSERT(endStyled < Length());
1333 if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
1334 if (!didChange) {
1335 startMod = endStyled;
1337 didChange = true;
1338 endMod = endStyled;
1341 if (didChange) {
1342 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1343 startMod, endMod - startMod + 1);
1344 NotifyModified(mh);
1346 enteredStyling--;
1347 return true;
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);
1365 NotifyModified(mh);
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);
1387 NotifyModified(mh);
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;
1426 NotifyModified(mh);
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);
1461 NotifyModified(mh);
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))
1469 return false;
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;
1476 delete []watchers;
1477 watchers = pwNew;
1478 lenWatchers++;
1479 return true;
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) {
1487 delete []watchers;
1488 watchers = 0;
1489 lenWatchers = 0;
1490 } else {
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];
1495 delete []watchers;
1496 watchers = pwNew;
1497 lenWatchers--;
1499 return true;
1502 return false;
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) {
1533 if (pos > 0) {
1534 --pos;
1535 char startChar = cb.CharAt(pos);
1536 if (IsWordPartSeparator(startChar)) {
1537 while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {
1538 --pos;
1541 if (pos > 0) {
1542 startChar = cb.CharAt(pos);
1543 --pos;
1544 if (IsLowerCase(startChar)) {
1545 while (pos > 0 && IsLowerCase(cb.CharAt(pos)))
1546 --pos;
1547 if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos)))
1548 ++pos;
1549 } else if (IsUpperCase(startChar)) {
1550 while (pos > 0 && IsUpperCase(cb.CharAt(pos)))
1551 --pos;
1552 if (!IsUpperCase(cb.CharAt(pos)))
1553 ++pos;
1554 } else if (IsADigit(startChar)) {
1555 while (pos > 0 && IsADigit(cb.CharAt(pos)))
1556 --pos;
1557 if (!IsADigit(cb.CharAt(pos)))
1558 ++pos;
1559 } else if (IsPunctuation(startChar)) {
1560 while (pos > 0 && IsPunctuation(cb.CharAt(pos)))
1561 --pos;
1562 if (!IsPunctuation(cb.CharAt(pos)))
1563 ++pos;
1564 } else if (isspacechar(startChar)) {
1565 while (pos > 0 && isspacechar(cb.CharAt(pos)))
1566 --pos;
1567 if (!isspacechar(cb.CharAt(pos)))
1568 ++pos;
1569 } else if (!isascii(startChar)) {
1570 while (pos > 0 && !isascii(cb.CharAt(pos)))
1571 --pos;
1572 if (isascii(cb.CharAt(pos)))
1573 ++pos;
1574 } else {
1575 ++pos;
1579 return 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)))
1587 ++pos;
1588 startChar = cb.CharAt(pos);
1590 if (!isascii(startChar)) {
1591 while (pos < length && !isascii(cb.CharAt(pos)))
1592 ++pos;
1593 } else if (IsLowerCase(startChar)) {
1594 while (pos < length && IsLowerCase(cb.CharAt(pos)))
1595 ++pos;
1596 } else if (IsUpperCase(startChar)) {
1597 if (IsLowerCase(cb.CharAt(pos + 1))) {
1598 ++pos;
1599 while (pos < length && IsLowerCase(cb.CharAt(pos)))
1600 ++pos;
1601 } else {
1602 while (pos < length && IsUpperCase(cb.CharAt(pos)))
1603 ++pos;
1605 if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1)))
1606 --pos;
1607 } else if (IsADigit(startChar)) {
1608 while (pos < length && IsADigit(cb.CharAt(pos)))
1609 ++pos;
1610 } else if (IsPunctuation(startChar)) {
1611 while (pos < length && IsPunctuation(cb.CharAt(pos)))
1612 ++pos;
1613 } else if (isspacechar(startChar)) {
1614 while (pos < length && isspacechar(cb.CharAt(pos)))
1615 ++pos;
1616 } else {
1617 ++pos;
1619 return 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);
1628 if (delta < 0) {
1629 while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))))
1630 pos--;
1631 pos++;
1632 } else {
1633 while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))))
1634 pos++;
1636 return pos;
1639 static char BraceOpposite(char ch) {
1640 switch (ch) {
1641 case '(':
1642 return ')';
1643 case ')':
1644 return '(';
1645 case '[':
1646 return ']';
1647 case ']':
1648 return '[';
1649 case '{':
1650 return '}';
1651 case '}':
1652 return '{';
1653 case '<':
1654 return '>';
1655 case '>':
1656 return '<';
1657 default:
1658 return '\0';
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);
1666 if (chSeek == '\0')
1667 return - 1;
1668 char styBrace = static_cast<char>(StyleAt(position) & stylingBitsMask);
1669 int direction = -1;
1670 if (chBrace == '(' || chBrace == '[' || chBrace == '{' || chBrace == '<')
1671 direction = 1;
1672 int depth = 1;
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)
1680 depth++;
1681 if (chAtPos == chSeek)
1682 depth--;
1683 if (depth == 0)
1684 return position;
1686 position = position + direction;
1688 return - 1;
1692 * Implementation of RegexSearchBase for the default built-in regular expression engine
1694 class BuiltinRegex : public RegexSearchBase {
1695 public:
1696 BuiltinRegex(CharClassify *charClassTable) : search(charClassTable), substituted(NULL) {}
1698 virtual ~BuiltinRegex() {
1699 delete substituted;
1702 virtual long FindText(Document *doc, int minPos, int maxPos, const char *s,
1703 bool caseSensitive, bool word, bool wordStart, int flags,
1704 int *length);
1706 virtual const char *SubstituteByPosition(Document *doc, const char *text, int *length);
1708 private:
1709 RESearch search;
1710 char *substituted;
1713 // Define a way for the Regular Expression code to access the document
1714 class DocumentIndexer : public CharacterIndexer {
1715 Document *pdoc;
1716 int end;
1717 public:
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)
1727 return 0;
1728 else
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,
1735 int *length) {
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);
1747 if (errmsg) {
1748 return -1;
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.
1760 lineRangeStart++;
1761 startPos = doc->LineStart(lineRangeStart);
1763 int pos = -1;
1764 int lenRet = 0;
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
1779 endOfLine = endPos;
1781 } else {
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);
1796 if (success) {
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);
1804 if (success) {
1805 if (search.eopat[0] <= minPos) {
1806 pos = search.bopat[0];
1807 lenRet = search.eopat[0] - search.bopat[0];
1808 } else {
1809 success = 0;
1814 break;
1817 *length = lenRet;
1818 return pos;
1821 const char *BuiltinRegex::SubstituteByPosition(Document *doc, const char *text, int *length) {
1822 delete []substituted;
1823 substituted = 0;
1824 DocumentIndexer di(doc, doc->Length());
1825 if (!search.GrabMatches(di))
1826 return 0;
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];
1833 i++;
1834 } else {
1835 switch (text[i + 1]) {
1836 case 'a':
1837 case 'b':
1838 case 'f':
1839 case 'n':
1840 case 'r':
1841 case 't':
1842 case 'v':
1843 case '\\':
1844 i++;
1846 lenResult++;
1848 } else {
1849 lenResult++;
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);
1861 o += len;
1862 j++;
1863 } else {
1864 j++;
1865 switch (text[j]) {
1866 case 'a':
1867 *o++ = '\a';
1868 break;
1869 case 'b':
1870 *o++ = '\b';
1871 break;
1872 case 'f':
1873 *o++ = '\f';
1874 break;
1875 case 'n':
1876 *o++ = '\n';
1877 break;
1878 case 'r':
1879 *o++ = '\r';
1880 break;
1881 case 't':
1882 *o++ = '\t';
1883 break;
1884 case 'v':
1885 *o++ = '\v';
1886 break;
1887 case '\\':
1888 *o++ = '\\';
1889 break;
1890 default:
1891 *o++ = '\\';
1892 j--;
1895 } else {
1896 *o++ = text[j];
1899 *o = '\0';
1900 *length = lenResult;
1901 return substituted;
1904 #ifndef SCI_OWNREGEX
1906 #ifdef SCI_NAMESPACE
1908 RegexSearchBase *Scintilla::CreateRegexSearch(CharClassify *charClassTable) {
1909 return new BuiltinRegex(charClassTable);
1912 #else
1914 RegexSearchBase *CreateRegexSearch(CharClassify *charClassTable) {
1915 return new BuiltinRegex(charClassTable);
1918 #endif
1920 #endif