]> git.saurik.com Git - wxWidgets.git/blame - src/stc/scintilla/src/Document.cxx
Added missing '!' to HasCapture() in window.cpp
[wxWidgets.git] / src / stc / scintilla / src / Document.cxx
CommitLineData
9ce192d4 1// Scintilla source code edit control
65ec6247
RD
2/** @file Document.cxx
3 ** Text document that handles notifications, DBCS, styling, words and end of line.
4 **/
5// Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
9ce192d4
RD
6// The License.txt file describes the conditions under which this software may be distributed.
7
8#include <stdlib.h>
9#include <string.h>
10#include <stdio.h>
11#include <ctype.h>
12
13#include "Platform.h"
14
15#include "Scintilla.h"
16#include "SVector.h"
17#include "CellBuffer.h"
18#include "Document.h"
65ec6247
RD
19#include "RESearch.h"
20
21// This is ASCII specific but is safe with chars >= 0x80
22inline bool isspacechar(unsigned char ch) {
23 return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
24}
9ce192d4
RD
25
26Document::Document() {
27 refCount = 0;
28#ifdef unix
29 eolMode = SC_EOL_LF;
30#else
31 eolMode = SC_EOL_CRLF;
32#endif
33 dbcsCodePage = 0;
34 stylingBits = 5;
35 stylingBitsMask = 0x1F;
9ce192d4
RD
36 stylingMask = 0;
37 for (int ch = 0; ch < 256; ch++) {
38 wordchars[ch] = isalnum(ch) || ch == '_';
39 }
40 endStyled = 0;
41 enteredCount = 0;
f6bcfd97 42 enteredReadOnlyCount = 0;
9ce192d4 43 tabInChars = 8;
f6bcfd97
BP
44 indentInChars = 0;
45 useTabs = true;
65ec6247
RD
46 tabIndents = true;
47 backspaceUnindents = false;
9ce192d4
RD
48 watchers = 0;
49 lenWatchers = 0;
65ec6247
RD
50
51 matchesValid = false;
52 pre = 0;
53 substituted = 0;
9ce192d4
RD
54}
55
56Document::~Document() {
57 for (int i = 0; i < lenWatchers; i++) {
58 watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
59 }
60 delete []watchers;
61 watchers = 0;
62 lenWatchers = 0;
65ec6247
RD
63 delete pre;
64 pre = 0;
65 delete []substituted;
66 substituted = 0;
9ce192d4
RD
67}
68
69// Increase reference count and return its previous value.
70int Document::AddRef() {
71 return refCount++;
72}
73
d134f170 74// Decrease reference count and return its previous value.
9ce192d4
RD
75// Delete the document if reference count reaches zero.
76int Document::Release() {
77 int curRefCount = --refCount;
78 if (curRefCount == 0)
79 delete this;
80 return curRefCount;
81}
82
83void Document::SetSavePoint() {
84 cb.SetSavePoint();
85 NotifySavePoint(true);
86}
87
65ec6247
RD
88int Document::AddMark(int line, int markerNum) {
89 int prev = cb.AddMark(line, markerNum);
f6bcfd97
BP
90 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
91 NotifyModified(mh);
92 return prev;
64a3ee5f
ES
93}
94
65ec6247
RD
95void Document::DeleteMark(int line, int markerNum) {
96 cb.DeleteMark(line, markerNum);
f6bcfd97
BP
97 DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0);
98 NotifyModified(mh);
88b780d9
RD
99}
100
65ec6247
RD
101void Document::DeleteMarkFromHandle(int markerHandle) {
102 cb.DeleteMarkFromHandle(markerHandle);
f6bcfd97
BP
103 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
104 NotifyModified(mh);
105}
106
65ec6247
RD
107void Document::DeleteAllMarks(int markerNum) {
108 cb.DeleteAllMarks(markerNum);
f6bcfd97
BP
109 DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
110 NotifyModified(mh);
111}
112
113int Document::LineStart(int line) {
114 return cb.LineStart(line);
115}
116
117int Document::LineEnd(int line) {
9ce192d4 118 if (line == LinesTotal() - 1) {
f6bcfd97 119 return LineStart(line + 1);
9ce192d4 120 } else {
f6bcfd97 121 int position = LineStart(line + 1) - 1;
9ce192d4
RD
122 // When line terminator is CR+LF, may need to go back one more
123 if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
124 position--;
125 }
f6bcfd97 126 return position;
9ce192d4 127 }
f6bcfd97
BP
128}
129
130int Document::LineFromPosition(int pos) {
131 return cb.LineFromPosition(pos);
132}
133
134int Document::LineEndPosition(int position) {
135 return LineEnd(LineFromPosition(position));
9ce192d4
RD
136}
137
138int Document::VCHomePosition(int position) {
139 int line = LineFromPosition(position);
140 int startPosition = LineStart(line);
141 int endLine = LineStart(line + 1) - 1;
142 int startText = startPosition;
143 while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
144 startText++;
145 if (position == startText)
146 return startPosition;
147 else
148 return startText;
149}
150
65ec6247
RD
151int Document::SetLevel(int line, int level) {
152 int prev = cb.SetLevel(line, level);
9ce192d4 153 if (prev != level) {
65ec6247
RD
154 DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
155 LineStart(line), 0, 0, 0);
9ce192d4
RD
156 mh.line = line;
157 mh.foldLevelNow = level;
158 mh.foldLevelPrev = prev;
159 NotifyModified(mh);
160 }
161 return prev;
162}
163
164static bool IsSubordinate(int levelStart, int levelTry) {
165 if (levelTry & SC_FOLDLEVELWHITEFLAG)
166 return true;
65ec6247 167 else
9ce192d4
RD
168 return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
169}
170
171int Document::GetLastChild(int lineParent, int level) {
172 if (level == -1)
173 level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
174 int maxLine = LinesTotal();
175 int lineMaxSubord = lineParent;
65ec6247
RD
176 while (lineMaxSubord < maxLine - 1) {
177 EnsureStyledTo(LineStart(lineMaxSubord + 2));
178 if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))
f6bcfd97 179 break;
9ce192d4
RD
180 lineMaxSubord++;
181 }
182 if (lineMaxSubord > lineParent) {
65ec6247
RD
183 if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) {
184 // Have chewed up some whitespace that belongs to a parent so seek back
185 if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) {
9ce192d4
RD
186 lineMaxSubord--;
187 }
188 }
189 }
190 return lineMaxSubord;
191}
192
193int Document::GetFoldParent(int line) {
194 int level = GetLevel(line);
65ec6247 195 int lineLook = line - 1;
9ce192d4 196 while ((lineLook > 0) && (
65ec6247
RD
197 (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
198 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
199 ) {
9ce192d4
RD
200 lineLook--;
201 }
202 if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
65ec6247 203 ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
9ce192d4
RD
204 return lineLook;
205 } else {
206 return -1;
207 }
208}
209
210int Document::ClampPositionIntoDocument(int pos) {
211 return Platform::Clamp(pos, 0, Length());
212}
213
214bool Document::IsCrLf(int pos) {
215 if (pos < 0)
216 return false;
217 if (pos >= (Length() - 1))
218 return false;
219 return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
220}
221
9ce192d4 222#if PLAT_WIN
d134f170 223bool Document::IsDBCS(int pos) {
9ce192d4 224 if (dbcsCodePage) {
f6bcfd97
BP
225 if (SC_CP_UTF8 == dbcsCodePage) {
226 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
227 return ch >= 0x80;
228 } else {
229 // Anchor DBCS calculations at start of line because start of line can
230 // not be a DBCS trail byte.
231 int startLine = pos;
232 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
233 startLine--;
234 while (startLine <= pos) {
235 if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) {
236 startLine++;
237 if (startLine >= pos)
238 return true;
239 }
9ce192d4 240 startLine++;
9ce192d4 241 }
9ce192d4
RD
242 }
243 }
244 return false;
d134f170 245}
9ce192d4 246#else
d134f170
RD
247// PLAT_GTK or PLAT_WX
248// TODO: support DBCS under GTK+ and WX
249bool Document::IsDBCS(int) {
9ce192d4 250 return false;
9ce192d4 251}
d134f170 252#endif
9ce192d4 253
f6bcfd97
BP
254int Document::LenChar(int pos) {
255 if (IsCrLf(pos)) {
256 return 2;
257 } else if (SC_CP_UTF8 == dbcsCodePage) {
258 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
259 if (ch < 0x80)
260 return 1;
261 int len = 2;
65ec6247 262 if (ch >= (0x80 + 0x40 + 0x20))
f6bcfd97
BP
263 len = 3;
264 int lengthDoc = Length();
265 if ((pos + len) > lengthDoc)
65ec6247
RD
266 return lengthDoc -pos;
267 else
f6bcfd97
BP
268 return len;
269 } else if (IsDBCS(pos)) {
270 return 2;
271 } else {
272 return 1;
273 }
274}
275
9ce192d4
RD
276// Normalise a position so that it is not halfway through a two byte character.
277// This can occur in two situations -
278// When lines are terminated with \r\n pairs which should be treated as one character.
279// When displaying DBCS text such as Japanese.
280// If moving, move the position in the indicated direction.
281int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
282 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
283 // If out of range, just return value - should be fixed up after
284 if (pos < 0)
285 return pos;
286 if (pos > Length())
287 return pos;
288
289 // Position 0 and Length() can not be between any two characters
290 if (pos == 0)
291 return pos;
292 if (pos == Length())
293 return pos;
294
295 // assert pos > 0 && pos < Length()
296 if (checkLineEnd && IsCrLf(pos - 1)) {
297 if (moveDir > 0)
298 return pos + 1;
299 else
300 return pos - 1;
301 }
302
303 // Not between CR and LF
304
305#if PLAT_WIN
306 if (dbcsCodePage) {
f6bcfd97
BP
307 if (SC_CP_UTF8 == dbcsCodePage) {
308 unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
309 while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
310 // ch is a trail byte
311 if (moveDir > 0)
312 pos++;
65ec6247 313 else
f6bcfd97
BP
314 pos--;
315 ch = static_cast<unsigned char>(cb.CharAt(pos));
316 }
317 } else {
318 // Anchor DBCS calculations at start of line because start of line can
319 // not be a DBCS trail byte.
320 int startLine = pos;
321 while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n')
322 startLine--;
323 bool atLeadByte = false;
324 while (startLine < pos) {
325 if (atLeadByte)
326 atLeadByte = false;
327 else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine)))
328 atLeadByte = true;
329 else
330 atLeadByte = false;
331 startLine++;
332 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
333 }
9ce192d4 334
65ec6247 335
f6bcfd97
BP
336 if (atLeadByte) {
337 // Position is between a lead byte and a trail byte
338 if (moveDir > 0)
339 return pos + 1;
340 else
341 return pos - 1;
342 }
9ce192d4
RD
343 }
344 }
345#endif
346
347 return pos;
348}
349
350void Document::ModifiedAt(int pos) {
351 if (endStyled > pos)
352 endStyled = pos;
353}
354
355// Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
356// SetStyleAt does not change the persistent state of a document
357
358// Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
359void Document::DeleteChars(int pos, int len) {
b8b0e402
RD
360 if (len == 0)
361 return;
65ec6247 362 if ((pos + len) > Length())
b8b0e402 363 return;
65ec6247 364 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
f6bcfd97
BP
365 enteredReadOnlyCount++;
366 NotifyModifyAttempt();
367 enteredReadOnlyCount--;
368 }
9ce192d4
RD
369 if (enteredCount == 0) {
370 enteredCount++;
9ce192d4 371 if (!cb.IsReadOnly()) {
f6bcfd97 372 NotifyModified(
65ec6247
RD
373 DocModification(
374 SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
375 pos, len,
376 0, 0));
9ce192d4
RD
377 int prevLinesTotal = LinesTotal();
378 bool startSavePoint = cb.IsSavePoint();
65ec6247 379 const char *text = cb.DeleteChars(pos * 2, len * 2);
9ce192d4
RD
380 if (startSavePoint && cb.IsCollectingUndo())
381 NotifySavePoint(!startSavePoint);
65ec6247
RD
382 if ((pos < Length()) || (pos == 0))
383 ModifiedAt(pos);
384 else
385 ModifiedAt(pos-1);
f6bcfd97 386 NotifyModified(
65ec6247
RD
387 DocModification(
388 SC_MOD_DELETETEXT | SC_PERFORMED_USER,
389 pos, len,
390 LinesTotal() - prevLinesTotal, text));
9ce192d4
RD
391 }
392 enteredCount--;
393 }
394}
395
396void Document::InsertStyledString(int position, char *s, int insertLength) {
65ec6247 397 if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
f6bcfd97
BP
398 enteredReadOnlyCount++;
399 NotifyModifyAttempt();
400 enteredReadOnlyCount--;
401 }
9ce192d4
RD
402 if (enteredCount == 0) {
403 enteredCount++;
9ce192d4 404 if (!cb.IsReadOnly()) {
f6bcfd97 405 NotifyModified(
65ec6247
RD
406 DocModification(
407 SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
408 position / 2, insertLength / 2,
409 0, 0));
9ce192d4
RD
410 int prevLinesTotal = LinesTotal();
411 bool startSavePoint = cb.IsSavePoint();
412 const char *text = cb.InsertString(position, s, insertLength);
413 if (startSavePoint && cb.IsCollectingUndo())
414 NotifySavePoint(!startSavePoint);
415 ModifiedAt(position / 2);
f6bcfd97 416 NotifyModified(
65ec6247
RD
417 DocModification(
418 SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
419 position / 2, insertLength / 2,
420 LinesTotal() - prevLinesTotal, text));
9ce192d4
RD
421 }
422 enteredCount--;
423 }
424}
425
426int Document::Undo() {
427 int newPos = 0;
428 if (enteredCount == 0) {
429 enteredCount++;
430 bool startSavePoint = cb.IsSavePoint();
431 int steps = cb.StartUndo();
f6bcfd97 432 //Platform::DebugPrintf("Steps=%d\n", steps);
65ec6247 433 for (int step = 0; step < steps; step++) {
9ce192d4 434 int prevLinesTotal = LinesTotal();
f6bcfd97
BP
435 const Action &action = cb.GetUndoStep();
436 if (action.at == removeAction) {
65ec6247
RD
437 NotifyModified(DocModification(
438 SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
439 } else {
440 NotifyModified(DocModification(
441 SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
442 }
f6bcfd97 443 cb.PerformUndoStep();
9ce192d4
RD
444 int cellPosition = action.position / 2;
445 ModifiedAt(cellPosition);
446 newPos = cellPosition;
65ec6247 447
9ce192d4
RD
448 int modFlags = SC_PERFORMED_UNDO;
449 // With undo, an insertion action becomes a deletion notification
450 if (action.at == removeAction) {
451 newPos += action.lenData;
452 modFlags |= SC_MOD_INSERTTEXT;
453 } else {
454 modFlags |= SC_MOD_DELETETEXT;
455 }
65ec6247 456 if (step == steps - 1)
9ce192d4 457 modFlags |= SC_LASTSTEPINUNDOREDO;
65ec6247
RD
458 NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
459 LinesTotal() - prevLinesTotal, action.data));
9ce192d4 460 }
65ec6247 461
9ce192d4
RD
462 bool endSavePoint = cb.IsSavePoint();
463 if (startSavePoint != endSavePoint)
464 NotifySavePoint(endSavePoint);
465 enteredCount--;
466 }
467 return newPos;
468}
469
470int Document::Redo() {
471 int newPos = 0;
472 if (enteredCount == 0) {
473 enteredCount++;
474 bool startSavePoint = cb.IsSavePoint();
475 int steps = cb.StartRedo();
65ec6247 476 for (int step = 0; step < steps; step++) {
9ce192d4 477 int prevLinesTotal = LinesTotal();
f6bcfd97
BP
478 const Action &action = cb.GetRedoStep();
479 if (action.at == insertAction) {
65ec6247
RD
480 NotifyModified(DocModification(
481 SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
482 } else {
483 NotifyModified(DocModification(
484 SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
485 }
f6bcfd97
BP
486 cb.PerformRedoStep();
487 ModifiedAt(action.position / 2);
488 newPos = action.position / 2;
65ec6247 489
9ce192d4
RD
490 int modFlags = SC_PERFORMED_REDO;
491 if (action.at == insertAction) {
492 newPos += action.lenData;
493 modFlags |= SC_MOD_INSERTTEXT;
494 } else {
495 modFlags |= SC_MOD_DELETETEXT;
496 }
65ec6247 497 if (step == steps - 1)
9ce192d4 498 modFlags |= SC_LASTSTEPINUNDOREDO;
f6bcfd97 499 NotifyModified(
65ec6247
RD
500 DocModification(modFlags, action.position / 2, action.lenData,
501 LinesTotal() - prevLinesTotal, action.data));
9ce192d4 502 }
65ec6247 503
9ce192d4
RD
504 bool endSavePoint = cb.IsSavePoint();
505 if (startSavePoint != endSavePoint)
506 NotifySavePoint(endSavePoint);
507 enteredCount--;
508 }
509 return newPos;
510}
511
512void Document::InsertChar(int pos, char ch) {
513 char chs[2];
514 chs[0] = ch;
515 chs[1] = 0;
516 InsertStyledString(pos*2, chs, 2);
517}
518
519// Insert a null terminated string
520void Document::InsertString(int position, const char *s) {
521 InsertString(position, s, strlen(s));
522}
523
524// Insert a string with a length
525void Document::InsertString(int position, const char *s, int insertLength) {
526 char *sWithStyle = new char[insertLength * 2];
527 if (sWithStyle) {
528 for (int i = 0; i < insertLength; i++) {
529 sWithStyle[i*2] = s[i];
530 sWithStyle[i*2 + 1] = 0;
531 }
532 InsertStyledString(position*2, sWithStyle, insertLength*2);
533 delete []sWithStyle;
534 }
535}
536
f6bcfd97
BP
537void Document::ChangeChar(int pos, char ch) {
538 DeleteChars(pos, 1);
539 InsertChar(pos, ch);
540}
541
9ce192d4 542void Document::DelChar(int pos) {
f6bcfd97 543 DeleteChars(pos, LenChar(pos));
9ce192d4
RD
544}
545
546int Document::DelCharBack(int pos) {
547 if (pos <= 0) {
548 return pos;
549 } else if (IsCrLf(pos - 2)) {
550 DeleteChars(pos - 2, 2);
551 return pos - 2;
f6bcfd97 552 } else if (SC_CP_UTF8 == dbcsCodePage) {
65ec6247 553 int startChar = MovePositionOutsideChar(pos - 1, -1, false);
f6bcfd97
BP
554 DeleteChars(startChar, pos - startChar);
555 return startChar;
9ce192d4
RD
556 } else if (IsDBCS(pos - 1)) {
557 DeleteChars(pos - 2, 2);
558 return pos - 2;
559 } else {
560 DeleteChars(pos - 1, 1);
561 return pos - 1;
562 }
563}
564
f6bcfd97
BP
565static bool isindentchar(char ch) {
566 return (ch == ' ') || (ch == '\t');
567}
568
569static int NextTab(int pos, int tabSize) {
570 return ((pos / tabSize) + 1) * tabSize;
571}
572
573static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
574 length--; // ensure space for \0
575 if (!insertSpaces) {
576 while ((indent >= tabSize) && (length > 0)) {
577 *linebuf++ = '\t';
578 indent -= tabSize;
579 length--;
9ce192d4 580 }
f6bcfd97
BP
581 }
582 while ((indent > 0) && (length > 0)) {
583 *linebuf++ = ' ';
584 indent--;
585 length--;
586 }
587 *linebuf = '\0';
588}
589
590int Document::GetLineIndentation(int line) {
591 int indent = 0;
592 if ((line >= 0) && (line < LinesTotal())) {
593 int lineStart = LineStart(line);
594 int length = Length();
65ec6247 595 for (int i = lineStart;i < length;i++) {
f6bcfd97
BP
596 char ch = cb.CharAt(i);
597 if (ch == ' ')
598 indent++;
599 else if (ch == '\t')
600 indent = NextTab(indent, tabInChars);
65ec6247 601 else
f6bcfd97 602 return indent;
9ce192d4
RD
603 }
604 }
f6bcfd97
BP
605 return indent;
606}
607
608void Document::SetLineIndentation(int line, int indent) {
609 int indentOfLine = GetLineIndentation(line);
610 if (indent < 0)
611 indent = 0;
612 if (indent != indentOfLine) {
613 char linebuf[1000];
614 CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
615 int thisLineStart = LineStart(line);
616 int indentPos = GetLineIndentPosition(line);
617 DeleteChars(thisLineStart, indentPos - thisLineStart);
618 InsertString(thisLineStart, linebuf);
619 }
620}
621
622int Document::GetLineIndentPosition(int line) {
65ec6247
RD
623 if (line < 0)
624 return 0;
f6bcfd97
BP
625 int pos = LineStart(line);
626 int length = Length();
627 while ((pos < length) && isindentchar(cb.CharAt(pos))) {
628 pos++;
629 }
630 return pos;
631}
632
d134f170
RD
633int Document::GetColumn(int pos) {
634 int column = 0;
635 int line = LineFromPosition(pos);
636 if ((line >= 0) && (line < LinesTotal())) {
65ec6247 637 for (int i = LineStart(line);i < pos;i++) {
d134f170
RD
638 char ch = cb.CharAt(i);
639 if (ch == '\t')
640 column = NextTab(column, tabInChars);
641 else if (ch == '\r')
642 return column;
643 else if (ch == '\n')
644 return column;
645 else
646 column++;
647 }
648 }
649 return column;
650}
651
f6bcfd97
BP
652void Document::Indent(bool forwards, int lineBottom, int lineTop) {
653 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
654 for (int line = lineBottom; line >= lineTop; line--) {
655 int indentOfLine = GetLineIndentation(line);
656 if (forwards)
657 SetLineIndentation(line, indentOfLine + IndentSize());
658 else
659 SetLineIndentation(line, indentOfLine - IndentSize());
660 }
9ce192d4
RD
661}
662
663void Document::ConvertLineEnds(int eolModeSet) {
664 BeginUndoAction();
665 for (int pos = 0; pos < Length(); pos++) {
666 if (cb.CharAt(pos) == '\r') {
65ec6247 667 if (cb.CharAt(pos + 1) == '\n') {
9ce192d4
RD
668 if (eolModeSet != SC_EOL_CRLF) {
669 DeleteChars(pos, 2);
670 if (eolModeSet == SC_EOL_CR)
671 InsertString(pos, "\r", 1);
672 else
673 InsertString(pos, "\n", 1);
674 } else {
675 pos++;
676 }
677 } else {
678 if (eolModeSet != SC_EOL_CR) {
679 DeleteChars(pos, 1);
680 if (eolModeSet == SC_EOL_CRLF) {
681 InsertString(pos, "\r\n", 2);
682 pos++;
683 } else {
684 InsertString(pos, "\n", 1);
685 }
686 }
687 }
688 } else if (cb.CharAt(pos) == '\n') {
689 if (eolModeSet != SC_EOL_LF) {
690 DeleteChars(pos, 1);
691 if (eolModeSet == SC_EOL_CRLF) {
692 InsertString(pos, "\r\n", 2);
693 pos++;
694 } else {
695 InsertString(pos, "\r", 1);
696 }
697 }
698 }
699 }
700 EndUndoAction();
701}
702
703bool Document::IsWordChar(unsigned char ch) {
65ec6247 704 if ((SC_CP_UTF8 == dbcsCodePage) && (ch > 0x80))
f6bcfd97 705 return true;
9ce192d4
RD
706 return wordchars[ch];
707}
708
709int Document::ExtendWordSelect(int pos, int delta) {
710 if (delta < 0) {
711 while (pos > 0 && IsWordChar(cb.CharAt(pos - 1)))
712 pos--;
713 } else {
714 while (pos < (Length()) && IsWordChar(cb.CharAt(pos)))
715 pos++;
716 }
717 return pos;
718}
719
720int Document::NextWordStart(int pos, int delta) {
721 if (delta < 0) {
722 while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t'))
723 pos--;
65ec6247
RD
724 if (isspacechar(cb.CharAt(pos - 1))) { // Back up to previous line
725 while (pos > 0 && isspacechar(cb.CharAt(pos - 1)))
9ce192d4
RD
726 pos--;
727 } else {
728 bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1));
65ec6247 729 while (pos > 0 && !isspacechar(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1))))
9ce192d4
RD
730 pos--;
731 }
732 } else {
733 bool startAtWordChar = IsWordChar(cb.CharAt(pos));
65ec6247 734 while (pos < (Length()) && isspacechar(cb.CharAt(pos)))
9ce192d4 735 pos++;
65ec6247 736 while (pos < (Length()) && !isspacechar(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos))))
9ce192d4
RD
737 pos++;
738 while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t'))
739 pos++;
740 }
741 return pos;
742}
743
65ec6247
RD
744/**
745 * Check that the character before the given position
746 * is not a word character.
747 */
d134f170
RD
748bool Document::IsWordStartAt(int pos) {
749 if (pos > 0) {
750 return !IsWordChar(CharAt(pos - 1));
9ce192d4 751 }
d134f170
RD
752 return true;
753}
754
65ec6247
RD
755/**
756 * Check that the character after the given position
757 * is not a word character.
758 */
d134f170
RD
759bool Document::IsWordEndAt(int pos) {
760 if (pos < Length() - 1) {
761 return !IsWordChar(CharAt(pos));
9ce192d4
RD
762 }
763 return true;
764}
765
65ec6247
RD
766/**
767 * Check that the given range is delimited by
768 * non word characters.
769 */
d134f170
RD
770bool Document::IsWordAt(int start, int end) {
771 return IsWordStartAt(start) && IsWordEndAt(end);
772}
773
65ec6247
RD
774// The comparison and case changing functions here assume ASCII
775// or extended ASCII such as the normal Windows code page.
776
777static inline char MakeUpperCase(char ch) {
778 if (ch < 'a' || ch > 'z')
779 return ch;
780 else
781 return static_cast<char>(ch - 'a' + 'A');
782}
783
784static inline char MakeLowerCase(char ch) {
785 if (ch < 'A' || ch > 'Z')
786 return ch;
787 else
788 return static_cast<char>(ch - 'A' + 'a');
789}
790
791// Define a way for the Regular Expression code to access the document
792class DocumentIndexer : public CharacterIndexer {
793 Document *pdoc;
794 int end;
795public:
796DocumentIndexer(Document *pdoc_, int end_) :
797 pdoc(pdoc_), end(end_) {}
798
799 virtual char CharAt(int index) {
800 if (index < 0 || index >= end)
801 return 0;
802 else
803 return pdoc->CharAt(index);
804 }
805};
806
807/**
808 * Find text in document, supporting both forward and backward
809 * searches (just pass minPos > maxPos to do a backward search)
810 * Has not been tested with backwards DBCS searches yet.
811 */
812long Document::FindText(int minPos, int maxPos, const char *s,
813 bool caseSensitive, bool word, bool wordStart, bool regExp,
814 int *length) {
815 if (regExp) {
816 if (!pre)
817 pre = new RESearch();
818 if (!pre)
819 return -1;
820
821 int startPos;
822 int endPos;
823
824 if (minPos <= maxPos) {
825 startPos = minPos;
826 endPos = maxPos;
9ce192d4 827 } else {
65ec6247
RD
828 startPos = maxPos;
829 endPos = minPos;
830 }
831
832 // Range endpoints should not be inside DBCS characters, but just in case, move them.
833 startPos = MovePositionOutsideChar(startPos, 1, false);
834 endPos = MovePositionOutsideChar(endPos, 1, false);
835
836 const char *errmsg = pre->Compile(s, *length, caseSensitive);
837 if (errmsg) {
838 return -1;
839 }
840 // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
841 // Replace first '.' with '-' in each property file variable reference:
842 // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
843 // Replace: $(\1-\2)
844 int lineRangeStart = LineFromPosition(startPos);
845 int lineRangeEnd = LineFromPosition(endPos);
846 if ((startPos >= LineEnd(lineRangeStart)) && (lineRangeStart < lineRangeEnd)) {
847 // the start position is at end of line or between line end characters.
848 lineRangeStart++;
849 startPos = LineStart(lineRangeStart);
850 }
851 int pos = -1;
852 int lenRet = 0;
853 char searchEnd = s[*length - 1];
854 if (*length == 1) {
855 // These produce empty selections so nudge them on if needed
856 if (s[0] == '^') {
857 if (startPos == LineStart(lineRangeStart))
858 startPos++;
859 } else if (s[0] == '$') {
860 if ((startPos == LineEnd(lineRangeStart)) && (lineRangeStart < lineRangeEnd))
861 startPos = LineStart(lineRangeStart + 1);
862 }
863 lineRangeStart = LineFromPosition(startPos);
864 lineRangeEnd = LineFromPosition(endPos);
865 }
866 for (int line = lineRangeStart; line <= lineRangeEnd; line++) {
867 int startOfLine = LineStart(line);
868 int endOfLine = LineEnd(line);
869 if (line == lineRangeStart) {
870 if ((startPos != startOfLine) && (s[0] == '^'))
871 continue; // Can't match start of line if start position after start of line
872 startOfLine = startPos;
873 }
874 if (line == lineRangeEnd) {
875 if ((endPos != endOfLine) && (searchEnd == '$'))
876 continue; // Can't match end of line if end position before end of line
877 endOfLine = endPos;
878 }
879 DocumentIndexer di(this, endOfLine);
880 int success = pre->Execute(di, startOfLine, endOfLine);
881 if (success) {
882 pos = pre->bopat[0];
883 lenRet = pre->eopat[0] - pre->bopat[0];
884 break;
885 }
886 }
887 *length = lenRet;
888 return pos;
889
890 } else {
891
892 bool forward = minPos <= maxPos;
893 int increment = forward ? 1 : -1;
894
895 // Range endpoints should not be inside DBCS characters, but just in case, move them.
896 int startPos = MovePositionOutsideChar(minPos, increment, false);
897 int endPos = MovePositionOutsideChar(maxPos, increment, false);
898
899 // Compute actual search ranges needed
900 int lengthFind = *length;
901 if (lengthFind == -1)
902 lengthFind = strlen(s);
903 int endSearch = endPos;
904 if (startPos <= endPos) {
905 endSearch = endPos - lengthFind + 1;
906 }
907 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
908 char firstChar = s[0];
909 if (!caseSensitive)
910 firstChar = static_cast<char>(MakeUpperCase(firstChar));
911 int pos = startPos;
912 while (forward ? (pos < endSearch) : (pos >= endSearch)) {
913 char ch = CharAt(pos);
914 if (caseSensitive) {
915 if (ch == firstChar) {
916 bool found = true;
917 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
918 ch = CharAt(pos + posMatch);
919 if (ch != s[posMatch])
920 found = false;
921 }
922 if (found) {
923 if ((!word && !wordStart) ||
924 word && IsWordAt(pos, pos + lengthFind) ||
925 wordStart && IsWordStartAt(pos))
926 return pos;
927 }
9ce192d4 928 }
65ec6247
RD
929 } else {
930 if (MakeUpperCase(ch) == firstChar) {
931 bool found = true;
932 for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
933 ch = CharAt(pos + posMatch);
934 if (MakeUpperCase(ch) != MakeUpperCase(s[posMatch]))
935 found = false;
936 }
937 if (found) {
938 if ((!word && !wordStart) ||
939 word && IsWordAt(pos, pos + lengthFind) ||
940 wordStart && IsWordStartAt(pos))
941 return pos;
942 }
9ce192d4
RD
943 }
944 }
65ec6247
RD
945 pos += increment;
946 if (dbcsCodePage) {
947 // Ensure trying to match from start of character
948 pos = MovePositionOutsideChar(pos, increment, false);
949 }
9ce192d4
RD
950 }
951 }
952 //Platform::DebugPrintf("Not found\n");
65ec6247
RD
953 return -1;
954}
955
956const char *Document::SubstituteByPosition(const char *text, int *length) {
957 if (!pre)
958 return 0;
959 delete []substituted;
960 substituted = 0;
961 DocumentIndexer di(this, Length());
962 if (!pre->GrabMatches(di))
963 return 0;
964 unsigned int lenResult = 0;
965 for (int i = 0; i < *length; i++) {
966 if ((text[i] == '\\') && (text[i + 1] >= '1' && text[i + 1] <= '9')) {
967 unsigned int patNum = text[i + 1] - '0';
968 lenResult += pre->eopat[patNum] - pre->bopat[patNum];
969 i++;
970 } else {
971 lenResult++;
972 }
973 }
974 substituted = new char[lenResult + 1];
975 if (!substituted)
976 return 0;
977 char *o = substituted;
978 for (int j = 0; j < *length; j++) {
979 if ((text[j] == '\\') && (text[j + 1] >= '1' && text[j + 1] <= '9')) {
980 unsigned int patNum = text[j + 1] - '0';
981 unsigned int len = pre->eopat[patNum] - pre->bopat[patNum];
982 if (pre->pat[patNum]) // Will be null if try for a match that did not occur
983 memcpy(o, pre->pat[patNum], len);
984 o += len;
985 j++;
986 } else {
987 *o++ = text[j];
988 }
989 }
990 *o = '\0';
991 *length = lenResult;
992 return substituted;
9ce192d4
RD
993}
994
995int Document::LinesTotal() {
996 return cb.Lines();
997}
998
f6bcfd97 999void Document::ChangeCase(Range r, bool makeUpperCase) {
65ec6247 1000 for (int pos = r.start; pos < r.end; pos++) {
f6bcfd97
BP
1001 char ch = CharAt(pos);
1002 if (dbcsCodePage && IsDBCS(pos)) {
1003 pos += LenChar(pos);
1004 } else {
1005 if (makeUpperCase) {
1006 if (islower(ch)) {
65ec6247 1007 ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
f6bcfd97
BP
1008 }
1009 } else {
1010 if (isupper(ch)) {
65ec6247 1011 ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
f6bcfd97
BP
1012 }
1013 }
1014 }
1015 }
1016}
1017
9ce192d4
RD
1018void Document::SetWordChars(unsigned char *chars) {
1019 int ch;
1020 for (ch = 0; ch < 256; ch++) {
1021 wordchars[ch] = false;
1022 }
1023 if (chars) {
1024 while (*chars) {
1025 wordchars[*chars] = true;
1026 chars++;
1027 }
1028 } else {
1029 for (ch = 0; ch < 256; ch++) {
1030 wordchars[ch] = isalnum(ch) || ch == '_';
1031 }
1032 }
1033}
1034
1035void Document::SetStylingBits(int bits) {
1036 stylingBits = bits;
1037 stylingBitsMask = 0;
65ec6247 1038 for (int bit = 0; bit < stylingBits; bit++) {
9ce192d4
RD
1039 stylingBitsMask <<= 1;
1040 stylingBitsMask |= 1;
1041 }
1042}
1043
1044void Document::StartStyling(int position, char mask) {
9ce192d4 1045 stylingMask = mask;
65ec6247 1046 endStyled = position;
9ce192d4
RD
1047}
1048
1049void Document::SetStyleFor(int length, char style) {
1050 if (enteredCount == 0) {
1051 enteredCount++;
1052 int prevEndStyled = endStyled;
65ec6247
RD
1053 if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {
1054 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1055 prevEndStyled, length);
9ce192d4
RD
1056 NotifyModified(mh);
1057 }
65ec6247 1058 endStyled += length;
9ce192d4
RD
1059 enteredCount--;
1060 }
1061}
1062
1063void Document::SetStyles(int length, char *styles) {
1064 if (enteredCount == 0) {
1065 enteredCount++;
1066 int prevEndStyled = endStyled;
1067 bool didChange = false;
65ec6247
RD
1068 for (int iPos = 0; iPos < length; iPos++, endStyled++) {
1069 if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
9ce192d4
RD
1070 didChange = true;
1071 }
1072 }
9ce192d4 1073 if (didChange) {
65ec6247
RD
1074 DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1075 prevEndStyled, endStyled - prevEndStyled);
9ce192d4
RD
1076 NotifyModified(mh);
1077 }
1078 enteredCount--;
1079 }
1080}
1081
f6bcfd97
BP
1082bool Document::EnsureStyledTo(int pos) {
1083 // Ask the watchers to style, and stop as soon as one responds.
1084 for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++)
1085 watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
1086 return pos <= GetEndStyled();
1087}
1088
9ce192d4
RD
1089bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
1090 for (int i = 0; i < lenWatchers; i++) {
1091 if ((watchers[i].watcher == watcher) &&
1092 (watchers[i].userData == userData))
1093 return false;
1094 }
1095 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
1096 if (!pwNew)
1097 return false;
1098 for (int j = 0; j < lenWatchers; j++)
1099 pwNew[j] = watchers[j];
1100 pwNew[lenWatchers].watcher = watcher;
1101 pwNew[lenWatchers].userData = userData;
1102 delete []watchers;
1103 watchers = pwNew;
1104 lenWatchers++;
1105 return true;
1106}
1107
1108bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
1109 for (int i = 0; i < lenWatchers; i++) {
1110 if ((watchers[i].watcher == watcher) &&
1111 (watchers[i].userData == userData)) {
1112 if (lenWatchers == 1) {
1113 delete []watchers;
1114 watchers = 0;
1115 lenWatchers = 0;
1116 } else {
1117 WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
1118 if (!pwNew)
1119 return false;
1120 for (int j = 0; j < lenWatchers - 1; j++) {
1121 pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
1122 }
1123 delete []watchers;
1124 watchers = pwNew;
1125 lenWatchers--;
1126 }
1127 return true;
1128 }
1129 }
1130 return false;
1131}
1132
1133void Document::NotifyModifyAttempt() {
1134 for (int i = 0; i < lenWatchers; i++) {
1135 watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
1136 }
1137}
1138
1139void Document::NotifySavePoint(bool atSavePoint) {
1140 for (int i = 0; i < lenWatchers; i++) {
1141 watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
1142 }
1143}
1144
1145void Document::NotifyModified(DocModification mh) {
1146 for (int i = 0; i < lenWatchers; i++) {
1147 watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
1148 }
1149}
65ec6247
RD
1150
1151bool Document::IsWordPartSeparator(char ch) {
1152 return ispunct(ch) && IsWordChar(ch);
1153}
1154
1155int Document::WordPartLeft(int pos) {
1156 if (pos > 0) {
1157 --pos;
1158 char startChar = cb.CharAt(pos);
1159 if (IsWordPartSeparator(startChar)) {
1160 while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {
1161 --pos;
1162 }
1163 }
1164 if (pos > 0) {
1165 startChar = cb.CharAt(pos);
1166 --pos;
1167 if (islower(startChar)) {
1168 while (pos > 0 && islower(cb.CharAt(pos)))
1169 --pos;
1170 if (!isupper(cb.CharAt(pos)) && !islower(cb.CharAt(pos)))
1171 ++pos;
1172 } else if (isupper(startChar)) {
1173 while (pos > 0 && isupper(cb.CharAt(pos)))
1174 --pos;
1175 if (!isupper(cb.CharAt(pos)))
1176 ++pos;
1177 } else if (isdigit(startChar)) {
1178 while (pos > 0 && isdigit(cb.CharAt(pos)))
1179 --pos;
1180 if (!isdigit(cb.CharAt(pos)))
1181 ++pos;
1182 } else if (ispunct(startChar)) {
1183 while (pos > 0 && ispunct(cb.CharAt(pos)))
1184 --pos;
1185 if (!ispunct(cb.CharAt(pos)))
1186 ++pos;
1187 } else if (isspacechar(startChar)) {
1188 while (pos > 0 && isspacechar(cb.CharAt(pos)))
1189 --pos;
1190 if (!isspacechar(cb.CharAt(pos)))
1191 ++pos;
1192 }
1193 }
1194 }
1195 return pos;
1196}
1197
1198int Document::WordPartRight(int pos) {
1199 char startChar = cb.CharAt(pos);
1200 int length = Length();
1201 if (IsWordPartSeparator(startChar)) {
1202 while (pos < length && IsWordPartSeparator(cb.CharAt(pos)))
1203 ++pos;
1204 startChar = cb.CharAt(pos);
1205 }
1206 if (islower(startChar)) {
1207 while (pos < length && islower(cb.CharAt(pos)))
1208 ++pos;
1209 } else if (isupper(startChar)) {
1210 if (islower(cb.CharAt(pos + 1))) {
1211 ++pos;
1212 while (pos < length && islower(cb.CharAt(pos)))
1213 ++pos;
1214 } else {
1215 while (pos < length && isupper(cb.CharAt(pos)))
1216 ++pos;
1217 }
1218 if (islower(cb.CharAt(pos)) && isupper(cb.CharAt(pos - 1)))
1219 --pos;
1220 } else if (isdigit(startChar)) {
1221 while (pos < length && isdigit(cb.CharAt(pos)))
1222 ++pos;
1223 } else if (ispunct(startChar)) {
1224 while (pos < length && ispunct(cb.CharAt(pos)))
1225 ++pos;
1226 } else if (isspacechar(startChar)) {
1227 while (pos < length && isspacechar(cb.CharAt(pos)))
1228 ++pos;
1229 }
1230 return pos;
1231}