]> git.saurik.com Git - wxWidgets.git/blame - src/stc/scintilla/src/Document.cxx
fixed wxListBox inheritance
[wxWidgets.git] / src / stc / scintilla / src / Document.cxx
CommitLineData
9ce192d4
RD
1// Scintilla source code edit control
2// Document.cxx - text document that handles notifications, DBCS, styling, words and end of line
3// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org>
4// The License.txt file describes the conditions under which this software may be distributed.
5
6#include <stdlib.h>
7#include <string.h>
8#include <stdio.h>
9#include <ctype.h>
10
11#include "Platform.h"
12
13#include "Scintilla.h"
14#include "SVector.h"
15#include "CellBuffer.h"
16#include "Document.h"
17
18Document::Document() {
19 refCount = 0;
20#ifdef unix
21 eolMode = SC_EOL_LF;
22#else
23 eolMode = SC_EOL_CRLF;
24#endif
25 dbcsCodePage = 0;
26 stylingBits = 5;
27 stylingBitsMask = 0x1F;
28 stylingPos = 0;
29 stylingMask = 0;
30 for (int ch = 0; ch < 256; ch++) {
31 wordchars[ch] = isalnum(ch) || ch == '_';
32 }
33 endStyled = 0;
34 enteredCount = 0;
f6bcfd97 35 enteredReadOnlyCount = 0;
9ce192d4 36 tabInChars = 8;
f6bcfd97
BP
37 indentInChars = 0;
38 useTabs = true;
9ce192d4
RD
39 watchers = 0;
40 lenWatchers = 0;
41}
42
43Document::~Document() {
44 for (int i = 0; i < lenWatchers; i++) {
45 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
46 }
47 delete []watchers;
48 watchers = 0;
49 lenWatchers = 0;
50}
51
52// Increase reference count and return its previous value.
53int Document::AddRef() {
54 return refCount++;
55}
56
d134f170 57// Decrease reference count and return its previous value.
9ce192d4
RD
58// Delete the document if reference count reaches zero.
59int Document::Release() {
60 int curRefCount = --refCount;
61 if (curRefCount == 0)
62 delete this;
63 return curRefCount;
64}
65
66void Document::SetSavePoint() {
67 cb.SetSavePoint();
68 NotifySavePoint(true);
69}
70
f6bcfd97
BP
71int Document::AddMark(int line, int markerNum) {
72 int prev = cb.AddMark(line, markerNum);
73 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
74 NotifyModified(mh);
75 return prev;
64a3ee5f
ES
76}
77
f6bcfd97
BP
78void Document::DeleteMark(int line, int markerNum) {
79 cb.DeleteMark(line, markerNum);
80 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
81 NotifyModified(mh);
88b780d9
RD
82}
83
f6bcfd97
BP
84void Document::DeleteMarkFromHandle(int markerHandle) {
85 cb.DeleteMarkFromHandle(markerHandle);
86 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
87 NotifyModified(mh);
88}
89
90void Document::DeleteAllMarks(int markerNum) {
91 cb.DeleteAllMarks(markerNum);
92 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
93 NotifyModified(mh);
94}
95
96int Document::LineStart(int line) {
97 return cb.LineStart(line);
98}
99
100int Document::LineEnd(int line) {
9ce192d4 101 if (line == LinesTotal() - 1) {
f6bcfd97 102 return LineStart(line + 1);
9ce192d4 103 } else {
f6bcfd97 104 int position = LineStart(line + 1) - 1;
9ce192d4
RD
105 // When line terminator is CR+LF, may need to go back one more
106 if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
107 position--;
108 }
f6bcfd97 109 return position;
9ce192d4 110 }
f6bcfd97
BP
111}
112
113int Document::LineFromPosition(int pos) {
114 return cb.LineFromPosition(pos);
115}
116
117int Document::LineEndPosition(int position) {
118 return LineEnd(LineFromPosition(position));
9ce192d4
RD
119}
120
121int Document::VCHomePosition(int position) {
122 int line = LineFromPosition(position);
123 int startPosition = LineStart(line);
124 int endLine = LineStart(line + 1) - 1;
125 int startText = startPosition;
126 while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
127 startText++;
128 if (position == startText)
129 return startPosition;
130 else
131 return startText;
132}
133
134int Document::SetLevel(int line, int level) {
135 int prev = cb.SetLevel(line, level);
136 if (prev != level) {
137 DocModification mh(SC_MOD_CHANGEFOLD, LineStart(line), 0, 0, 0);
138 mh.line = line;
139 mh.foldLevelNow = level;
140 mh.foldLevelPrev = prev;
141 NotifyModified(mh);
142 }
143 return prev;
144}
145
146static bool IsSubordinate(int levelStart, int levelTry) {
147 if (levelTry & SC_FOLDLEVELWHITEFLAG)
148 return true;
149 else
150 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
151}
152
153int Document::GetLastChild(int lineParent, int level) {
154 if (level == -1)
155 level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
156 int maxLine = LinesTotal();
157 int lineMaxSubord = lineParent;
f6bcfd97
BP
158 while (lineMaxSubord < maxLine-1) {
159 EnsureStyledTo(LineStart(lineMaxSubord+2));
160 if (!IsSubordinate(level, GetLevel(lineMaxSubord+1)))
161 break;
9ce192d4
RD
162 lineMaxSubord++;
163 }
164 if (lineMaxSubord > lineParent) {
165 if (level > (GetLevel(lineMaxSubord+1) & SC_FOLDLEVELNUMBERMASK)) {
166 // Have chewed up some whitespace that belongs to a parent so seek back
167 if ((lineMaxSubord > lineParent) && (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG)) {
168 lineMaxSubord--;
169 }
170 }
171 }
172 return lineMaxSubord;
173}
174
175int Document::GetFoldParent(int line) {
176 int level = GetLevel(line);
177 int lineLook = line-1;
178 while ((lineLook > 0) && (
179 (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
180 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
181 ) {
182 lineLook--;
183 }
184 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
185 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
186 return lineLook;
187 } else {
188 return -1;
189 }
190}
191
192int Document::ClampPositionIntoDocument(int pos) {
193 return Platform::Clamp(pos, 0, Length());
194}
195
196bool Document::IsCrLf(int pos) {
197 if (pos < 0)
198 return false;
199 if (pos >= (Length() - 1))
200 return false;
201 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
202}
203
9ce192d4 204#if PLAT_WIN
d134f170 205bool Document::IsDBCS(int pos) {
9ce192d4 206 if (dbcsCodePage) {
f6bcfd97
BP
207 if (SC_CP_UTF8 == dbcsCodePage) {
208 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
209 return ch >= 0x80;
210 } else {
211 // Anchor DBCS calculations at start of line because start of line can
212 // not be a DBCS trail byte.
213 int startLine = pos;
214 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
215 startLine--;
216 while (startLine <= pos) {
217 if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) {
218 startLine++;
219 if (startLine >= pos)
220 return true;
221 }
9ce192d4 222 startLine++;
9ce192d4 223 }
9ce192d4
RD
224 }
225 }
226 return false;
d134f170 227}
9ce192d4 228#else
d134f170
RD
229// PLAT_GTK or PLAT_WX
230// TODO: support DBCS under GTK+ and WX
231bool Document::IsDBCS(int) {
9ce192d4 232 return false;
9ce192d4 233}
d134f170 234#endif
9ce192d4 235
f6bcfd97
BP
236int Document::LenChar(int pos) {
237 if (IsCrLf(pos)) {
238 return 2;
239 } else if (SC_CP_UTF8 == dbcsCodePage) {
240 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
241 if (ch < 0x80)
242 return 1;
243 int len = 2;
244 if (ch >= (0x80+0x40+0x20))
245 len = 3;
246 int lengthDoc = Length();
247 if ((pos + len) > lengthDoc)
248 return lengthDoc-pos;
249 else
250 return len;
251 } else if (IsDBCS(pos)) {
252 return 2;
253 } else {
254 return 1;
255 }
256}
257
9ce192d4
RD
258// Normalise a position so that it is not halfway through a two byte character.
259// This can occur in two situations -
260// When lines are terminated with \r\n pairs which should be treated as one character.
261// When displaying DBCS text such as Japanese.
262// If moving, move the position in the indicated direction.
263int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
264 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
265 // If out of range, just return value - should be fixed up after
266 if (pos < 0)
267 return pos;
268 if (pos > Length())
269 return pos;
270
271 // Position 0 and Length() can not be between any two characters
272 if (pos == 0)
273 return pos;
274 if (pos == Length())
275 return pos;
276
277 // assert pos > 0 && pos < Length()
278 if (checkLineEnd && IsCrLf(pos - 1)) {
279 if (moveDir > 0)
280 return pos + 1;
281 else
282 return pos - 1;
283 }
284
285 // Not between CR and LF
286
287#if PLAT_WIN
288 if (dbcsCodePage) {
f6bcfd97
BP
289 if (SC_CP_UTF8 == dbcsCodePage) {
290 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
291 while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
292 // ch is a trail byte
293 if (moveDir > 0)
294 pos++;
295 else
296 pos--;
297 ch = static_cast<unsigned char>(cb.CharAt(pos));
298 }
299 } else {
300 // Anchor DBCS calculations at start of line because start of line can
301 // not be a DBCS trail byte.
302 int startLine = pos;
303 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
304 startLine--;
305 bool atLeadByte = false;
306 while (startLine < pos) {
307 if (atLeadByte)
308 atLeadByte = false;
309 else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine)))
310 atLeadByte = true;
311 else
312 atLeadByte = false;
313 startLine++;
314 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
315 }
9ce192d4 316
f6bcfd97
BP
317 if (atLeadByte) {
318 // Position is between a lead byte and a trail byte
319 if (moveDir > 0)
320 return pos + 1;
321 else
322 return pos - 1;
323 }
9ce192d4
RD
324 }
325 }
326#endif
327
328 return pos;
329}
330
331void Document::ModifiedAt(int pos) {
332 if (endStyled > pos)
333 endStyled = pos;
334}
335
336// Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
337// SetStyleAt does not change the persistent state of a document
338
339// Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
340void Document::DeleteChars(int pos, int len) {
d134f170
RD
341 if ((pos + len) > Length())
342 return;
f6bcfd97
BP
343 if (cb.IsReadOnly() && enteredReadOnlyCount==0) {
344 enteredReadOnlyCount++;
345 NotifyModifyAttempt();
346 enteredReadOnlyCount--;
347 }
9ce192d4
RD
348 if (enteredCount == 0) {
349 enteredCount++;
9ce192d4 350 if (!cb.IsReadOnly()) {
f6bcfd97
BP
351 NotifyModified(
352 DocModification(
353 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
354 pos, len,
355 0, 0));
9ce192d4
RD
356 int prevLinesTotal = LinesTotal();
357 bool startSavePoint = cb.IsSavePoint();
358 const char *text = cb.DeleteChars(pos*2, len * 2);
359 if (startSavePoint && cb.IsCollectingUndo())
360 NotifySavePoint(!startSavePoint);
361 ModifiedAt(pos);
f6bcfd97
BP
362 NotifyModified(
363 DocModification(
364 SC_MOD_DELETETEXT | SC_PERFORMED_USER,
365 pos, len,
366 LinesTotal() - prevLinesTotal, text));
9ce192d4
RD
367 }
368 enteredCount--;
369 }
370}
371
372void Document::InsertStyledString(int position, char *s, int insertLength) {
f6bcfd97
BP
373 if (cb.IsReadOnly() && enteredReadOnlyCount==0) {
374 enteredReadOnlyCount++;
375 NotifyModifyAttempt();
376 enteredReadOnlyCount--;
377 }
9ce192d4
RD
378 if (enteredCount == 0) {
379 enteredCount++;
9ce192d4 380 if (!cb.IsReadOnly()) {
f6bcfd97
BP
381 NotifyModified(
382 DocModification(
383 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
384 position / 2, insertLength / 2,
385 0, 0));
9ce192d4
RD
386 int prevLinesTotal = LinesTotal();
387 bool startSavePoint = cb.IsSavePoint();
388 const char *text = cb.InsertString(position, s, insertLength);
389 if (startSavePoint && cb.IsCollectingUndo())
390 NotifySavePoint(!startSavePoint);
391 ModifiedAt(position / 2);
f6bcfd97
BP
392 NotifyModified(
393 DocModification(
394 SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
395 position / 2, insertLength / 2,
396 LinesTotal() - prevLinesTotal, text));
9ce192d4
RD
397 }
398 enteredCount--;
399 }
400}
401
402int Document::Undo() {
403 int newPos = 0;
404 if (enteredCount == 0) {
405 enteredCount++;
406 bool startSavePoint = cb.IsSavePoint();
407 int steps = cb.StartUndo();
f6bcfd97 408 //Platform::DebugPrintf("Steps=%d\n", steps);
9ce192d4
RD
409 for (int step=0; step<steps; step++) {
410 int prevLinesTotal = LinesTotal();
f6bcfd97
BP
411 const Action &action = cb.GetUndoStep();
412 if (action.at == removeAction) {
413 NotifyModified(DocModification(
414 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
415 } else {
416 NotifyModified(DocModification(
417 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
418 }
419 cb.PerformUndoStep();
9ce192d4
RD
420 int cellPosition = action.position / 2;
421 ModifiedAt(cellPosition);
422 newPos = cellPosition;
423
424 int modFlags = SC_PERFORMED_UNDO;
425 // With undo, an insertion action becomes a deletion notification
426 if (action.at == removeAction) {
427 newPos += action.lenData;
428 modFlags |= SC_MOD_INSERTTEXT;
429 } else {
430 modFlags |= SC_MOD_DELETETEXT;
431 }
432 if (step == steps-1)
433 modFlags |= SC_LASTSTEPINUNDOREDO;
434 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
435 LinesTotal() - prevLinesTotal, action.data));
436 }
437
438 bool endSavePoint = cb.IsSavePoint();
439 if (startSavePoint != endSavePoint)
440 NotifySavePoint(endSavePoint);
441 enteredCount--;
442 }
443 return newPos;
444}
445
446int Document::Redo() {
447 int newPos = 0;
448 if (enteredCount == 0) {
449 enteredCount++;
450 bool startSavePoint = cb.IsSavePoint();
451 int steps = cb.StartRedo();
452 for (int step=0; step<steps; step++) {
453 int prevLinesTotal = LinesTotal();
f6bcfd97
BP
454 const Action &action = cb.GetRedoStep();
455 if (action.at == insertAction) {
456 NotifyModified(DocModification(
457 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
458 } else {
459 NotifyModified(DocModification(
460 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
461 }
462 cb.PerformRedoStep();
463 ModifiedAt(action.position / 2);
464 newPos = action.position / 2;
9ce192d4
RD
465
466 int modFlags = SC_PERFORMED_REDO;
467 if (action.at == insertAction) {
468 newPos += action.lenData;
469 modFlags |= SC_MOD_INSERTTEXT;
470 } else {
471 modFlags |= SC_MOD_DELETETEXT;
472 }
473 if (step == steps-1)
474 modFlags |= SC_LASTSTEPINUNDOREDO;
f6bcfd97
BP
475 NotifyModified(
476 DocModification(modFlags, action.position / 2, action.lenData,
9ce192d4
RD
477 LinesTotal() - prevLinesTotal, action.data));
478 }
479
480 bool endSavePoint = cb.IsSavePoint();
481 if (startSavePoint != endSavePoint)
482 NotifySavePoint(endSavePoint);
483 enteredCount--;
484 }
485 return newPos;
486}
487
488void Document::InsertChar(int pos, char ch) {
489 char chs[2];
490 chs[0] = ch;
491 chs[1] = 0;
492 InsertStyledString(pos*2, chs, 2);
493}
494
495// Insert a null terminated string
496void Document::InsertString(int position, const char *s) {
497 InsertString(position, s, strlen(s));
498}
499
500// Insert a string with a length
501void Document::InsertString(int position, const char *s, int insertLength) {
502 char *sWithStyle = new char[insertLength * 2];
503 if (sWithStyle) {
504 for (int i = 0; i < insertLength; i++) {
505 sWithStyle[i*2] = s[i];
506 sWithStyle[i*2 + 1] = 0;
507 }
508 InsertStyledString(position*2, sWithStyle, insertLength*2);
509 delete []sWithStyle;
510 }
511}
512
f6bcfd97
BP
513void Document::ChangeChar(int pos, char ch) {
514 DeleteChars(pos, 1);
515 InsertChar(pos, ch);
516}
517
9ce192d4 518void Document::DelChar(int pos) {
f6bcfd97 519 DeleteChars(pos, LenChar(pos));
9ce192d4
RD
520}
521
522int Document::DelCharBack(int pos) {
523 if (pos <= 0) {
524 return pos;
525 } else if (IsCrLf(pos - 2)) {
526 DeleteChars(pos - 2, 2);
527 return pos - 2;
f6bcfd97
BP
528 } else if (SC_CP_UTF8 == dbcsCodePage) {
529 int startChar = MovePositionOutsideChar(pos-1, -1, false);
530 DeleteChars(startChar, pos - startChar);
531 return startChar;
9ce192d4
RD
532 } else if (IsDBCS(pos - 1)) {
533 DeleteChars(pos - 2, 2);
534 return pos - 2;
535 } else {
536 DeleteChars(pos - 1, 1);
537 return pos - 1;
538 }
539}
540
f6bcfd97
BP
541static bool isindentchar(char ch) {
542 return (ch == ' ') || (ch == '\t');
543}
544
545static int NextTab(int pos, int tabSize) {
546 return ((pos / tabSize) + 1) * tabSize;
547}
548
549static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
550 length--; // ensure space for \0
551 if (!insertSpaces) {
552 while ((indent >= tabSize) && (length > 0)) {
553 *linebuf++ = '\t';
554 indent -= tabSize;
555 length--;
9ce192d4 556 }
f6bcfd97
BP
557 }
558 while ((indent > 0) && (length > 0)) {
559 *linebuf++ = ' ';
560 indent--;
561 length--;
562 }
563 *linebuf = '\0';
564}
565
566int Document::GetLineIndentation(int line) {
567 int indent = 0;
568 if ((line >= 0) && (line < LinesTotal())) {
569 int lineStart = LineStart(line);
570 int length = Length();
571 for (int i=lineStart;i<length;i++) {
572 char ch = cb.CharAt(i);
573 if (ch == ' ')
574 indent++;
575 else if (ch == '\t')
576 indent = NextTab(indent, tabInChars);
577 else
578 return indent;
9ce192d4
RD
579 }
580 }
f6bcfd97
BP
581 return indent;
582}
583
584void Document::SetLineIndentation(int line, int indent) {
585 int indentOfLine = GetLineIndentation(line);
586 if (indent < 0)
587 indent = 0;
588 if (indent != indentOfLine) {
589 char linebuf[1000];
590 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
591 int thisLineStart = LineStart(line);
592 int indentPos = GetLineIndentPosition(line);
593 DeleteChars(thisLineStart, indentPos - thisLineStart);
594 InsertString(thisLineStart, linebuf);
595 }
596}
597
598int Document::GetLineIndentPosition(int line) {
d134f170
RD
599 if (line < 0)
600 return 0;
f6bcfd97
BP
601 int pos = LineStart(line);
602 int length = Length();
603 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
604 pos++;
605 }
606 return pos;
607}
608
d134f170
RD
609int Document::GetColumn(int pos) {
610 int column = 0;
611 int line = LineFromPosition(pos);
612 if ((line >= 0) && (line < LinesTotal())) {
613 for (int i=LineStart(line);i<pos;i++) {
614 char ch = cb.CharAt(i);
615 if (ch == '\t')
616 column = NextTab(column, tabInChars);
617 else if (ch == '\r')
618 return column;
619 else if (ch == '\n')
620 return column;
621 else
622 column++;
623 }
624 }
625 return column;
626}
627
f6bcfd97
BP
628void Document::Indent(bool forwards, int lineBottom, int lineTop) {
629 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
630 for (int line = lineBottom; line >= lineTop; line--) {
631 int indentOfLine = GetLineIndentation(line);
632 if (forwards)
633 SetLineIndentation(line, indentOfLine + IndentSize());
634 else
635 SetLineIndentation(line, indentOfLine - IndentSize());
636 }
9ce192d4
RD
637}
638
639void Document::ConvertLineEnds(int eolModeSet) {
640 BeginUndoAction();
641 for (int pos = 0; pos < Length(); pos++) {
642 if (cb.CharAt(pos) == '\r') {
643 if (cb.CharAt(pos+1) == '\n') {
644 if (eolModeSet != SC_EOL_CRLF) {
645 DeleteChars(pos, 2);
646 if (eolModeSet == SC_EOL_CR)
647 InsertString(pos, "\r", 1);
648 else
649 InsertString(pos, "\n", 1);
650 } else {
651 pos++;
652 }
653 } else {
654 if (eolModeSet != SC_EOL_CR) {
655 DeleteChars(pos, 1);
656 if (eolModeSet == SC_EOL_CRLF) {
657 InsertString(pos, "\r\n", 2);
658 pos++;
659 } else {
660 InsertString(pos, "\n", 1);
661 }
662 }
663 }
664 } else if (cb.CharAt(pos) == '\n') {
665 if (eolModeSet != SC_EOL_LF) {
666 DeleteChars(pos, 1);
667 if (eolModeSet == SC_EOL_CRLF) {
668 InsertString(pos, "\r\n", 2);
669 pos++;
670 } else {
671 InsertString(pos, "\r", 1);
672 }
673 }
674 }
675 }
676 EndUndoAction();
677}
678
679bool Document::IsWordChar(unsigned char ch) {
f6bcfd97
BP
680 if ((SC_CP_UTF8 == dbcsCodePage) && (ch >0x80))
681 return true;
9ce192d4
RD
682 return wordchars[ch];
683}
684
685int Document::ExtendWordSelect(int pos, int delta) {
686 if (delta < 0) {
687 while (pos > 0 && IsWordChar(cb.CharAt(pos - 1)))
688 pos--;
689 } else {
690 while (pos < (Length()) && IsWordChar(cb.CharAt(pos)))
691 pos++;
692 }
693 return pos;
694}
695
696int Document::NextWordStart(int pos, int delta) {
697 if (delta < 0) {
698 while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t'))
699 pos--;
700 if (isspace(cb.CharAt(pos - 1))) { // Back up to previous line
701 while (pos > 0 && isspace(cb.CharAt(pos - 1)))
702 pos--;
703 } else {
704 bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1));
705 while (pos > 0 && !isspace(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1))))
706 pos--;
707 }
708 } else {
709 bool startAtWordChar = IsWordChar(cb.CharAt(pos));
710 while (pos < (Length()) && isspace(cb.CharAt(pos)))
711 pos++;
712 while (pos < (Length()) && !isspace(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos))))
713 pos++;
714 while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t'))
715 pos++;
716 }
717 return pos;
718}
719
d134f170
RD
720bool Document::IsWordStartAt(int pos) {
721 if (pos > 0) {
722 return !IsWordChar(CharAt(pos - 1));
9ce192d4 723 }
d134f170
RD
724 return true;
725}
726
727bool Document::IsWordEndAt(int pos) {
728 if (pos < Length() - 1) {
729 return !IsWordChar(CharAt(pos));
9ce192d4
RD
730 }
731 return true;
732}
733
d134f170
RD
734bool Document::IsWordAt(int start, int end) {
735 return IsWordStartAt(start) && IsWordEndAt(end);
736}
737
9ce192d4
RD
738// Find text in document, supporting both forward and backward
739// searches (just pass minPos > maxPos to do a backward search)
740// Has not been tested with backwards DBCS searches yet.
d134f170
RD
741long Document::FindText(int minPos, int maxPos, const char *s,
742 bool caseSensitive, bool word, bool wordStart) {
9ce192d4
RD
743 bool forward = minPos <= maxPos;
744 int increment = forward ? 1 : -1;
745
746 // Range endpoints should not be inside DBCS characters, but just in case, move them.
747 int startPos = MovePositionOutsideChar(minPos, increment, false);
748 int endPos = MovePositionOutsideChar(maxPos, increment, false);
749
750 // Compute actual search ranges needed
751 int lengthFind = strlen(s);
f6bcfd97 752 int endSearch = endPos;
9ce192d4
RD
753 if (startPos <= endPos) {
754 endSearch = endPos - lengthFind + 1;
9ce192d4
RD
755 }
756 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
757 char firstChar = s[0];
758 if (!caseSensitive)
f6bcfd97 759 firstChar = static_cast<char>(toupper(firstChar));
9ce192d4
RD
760 int pos = startPos;
761 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
762 char ch = CharAt(pos);
763 if (caseSensitive) {
764 if (ch == firstChar) {
765 bool found = true;
766 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
767 ch = CharAt(pos + posMatch);
768 if (ch != s[posMatch])
769 found = false;
770 }
771 if (found) {
d134f170
RD
772 if ((!word && !wordStart) ||
773 word && IsWordAt(pos, pos + lengthFind) ||
774 wordStart && IsWordStartAt(pos))
775 return pos;
9ce192d4
RD
776 }
777 }
778 } else {
779 if (toupper(ch) == firstChar) {
780 bool found = true;
781 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
782 ch = CharAt(pos + posMatch);
783 if (toupper(ch) != toupper(s[posMatch]))
784 found = false;
785 }
786 if (found) {
d134f170
RD
787 if (!(word && wordStart) ||
788 word && IsWordAt(pos, pos + lengthFind) ||
789 wordStart && IsWordStartAt(pos))
790 return pos;
9ce192d4
RD
791 }
792 }
793 }
794 pos += increment;
795 if (dbcsCodePage) {
796 // Ensure trying to match from start of character
797 pos = MovePositionOutsideChar(pos, increment, false);
798 }
799 }
800 //Platform::DebugPrintf("Not found\n");
801 return - 1;
802}
803
804int Document::LinesTotal() {
805 return cb.Lines();
806}
807
f6bcfd97
BP
808void Document::ChangeCase(Range r, bool makeUpperCase) {
809 for (int pos=r.start; pos<r.end; pos++) {
810 char ch = CharAt(pos);
811 if (dbcsCodePage && IsDBCS(pos)) {
812 pos += LenChar(pos);
813 } else {
814 if (makeUpperCase) {
815 if (islower(ch)) {
816 ChangeChar(pos, static_cast<char>(toupper(ch)));
817 }
818 } else {
819 if (isupper(ch)) {
820 ChangeChar(pos, static_cast<char>(tolower(ch)));
821 }
822 }
823 }
824 }
825}
826
9ce192d4
RD
827void Document::SetWordChars(unsigned char *chars) {
828 int ch;
829 for (ch = 0; ch < 256; ch++) {
830 wordchars[ch] = false;
831 }
832 if (chars) {
833 while (*chars) {
834 wordchars[*chars] = true;
835 chars++;
836 }
837 } else {
838 for (ch = 0; ch < 256; ch++) {
839 wordchars[ch] = isalnum(ch) || ch == '_';
840 }
841 }
842}
843
844void Document::SetStylingBits(int bits) {
845 stylingBits = bits;
846 stylingBitsMask = 0;
847 for (int bit=0; bit<stylingBits; bit++) {
848 stylingBitsMask <<= 1;
849 stylingBitsMask |= 1;
850 }
851}
852
853void Document::StartStyling(int position, char mask) {
854 stylingPos = position;
855 stylingMask = mask;
856}
857
858void Document::SetStyleFor(int length, char style) {
859 if (enteredCount == 0) {
860 enteredCount++;
861 int prevEndStyled = endStyled;
862 if (cb.SetStyleFor(stylingPos, length, style, stylingMask)) {
863 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
864 prevEndStyled, length);
865 NotifyModified(mh);
866 }
867 stylingPos += length;
868 endStyled = stylingPos;
869 enteredCount--;
870 }
871}
872
873void Document::SetStyles(int length, char *styles) {
874 if (enteredCount == 0) {
875 enteredCount++;
876 int prevEndStyled = endStyled;
877 bool didChange = false;
878 for (int iPos = 0; iPos < length; iPos++, stylingPos++) {
879 if (cb.SetStyleAt(stylingPos, styles[iPos], stylingMask)) {
880 didChange = true;
881 }
882 }
883 endStyled = stylingPos;
884 if (didChange) {
885 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
886 prevEndStyled, endStyled - prevEndStyled);
887 NotifyModified(mh);
888 }
889 enteredCount--;
890 }
891}
892
f6bcfd97
BP
893bool Document::EnsureStyledTo(int pos) {
894 // Ask the watchers to style, and stop as soon as one responds.
895 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++)
896 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
897 return pos <= GetEndStyled();
898}
899
9ce192d4
RD
900bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
901 for (int i = 0; i < lenWatchers; i++) {
902 if ((watchers[i].watcher == watcher) &&
903 (watchers[i].userData == userData))
904 return false;
905 }
906 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
907 if (!pwNew)
908 return false;
909 for (int j = 0; j < lenWatchers; j++)
910 pwNew[j] = watchers[j];
911 pwNew[lenWatchers].watcher = watcher;
912 pwNew[lenWatchers].userData = userData;
913 delete []watchers;
914 watchers = pwNew;
915 lenWatchers++;
916 return true;
917}
918
919bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
920 for (int i = 0; i < lenWatchers; i++) {
921 if ((watchers[i].watcher == watcher) &&
922 (watchers[i].userData == userData)) {
923 if (lenWatchers == 1) {
924 delete []watchers;
925 watchers = 0;
926 lenWatchers = 0;
927 } else {
928 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
929 if (!pwNew)
930 return false;
931 for (int j = 0; j < lenWatchers - 1; j++) {
932 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
933 }
934 delete []watchers;
935 watchers = pwNew;
936 lenWatchers--;
937 }
938 return true;
939 }
940 }
941 return false;
942}
943
944void Document::NotifyModifyAttempt() {
945 for (int i = 0; i < lenWatchers; i++) {
946 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
947 }
948}
949
950void Document::NotifySavePoint(bool atSavePoint) {
951 for (int i = 0; i < lenWatchers; i++) {
952 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
953 }
954}
955
956void Document::NotifyModified(DocModification mh) {
957 for (int i = 0; i < lenWatchers; i++) {
958 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
959 }
960}