~ubuntu-branches/ubuntu/wily/apparmor/wily

« back to all changes in this revision

Viewing changes to deprecated/management/profile-editor/src/wxStyledTextCtrl/Document.cxx

  • Committer: Bazaar Package Importer
  • Author(s): Kees Cook
  • Date: 2011-04-27 10:38:07 UTC
  • mfrom: (5.1.118 natty)
  • Revision ID: james.westby@ubuntu.com-20110427103807-ym3rhwys6o84ith0
Tags: 2.6.1-2
debian/copyright: clarify for some full organization names.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
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"
 
19
#include "RESearch.h"
 
20
 
 
21
// This is ASCII specific but is safe with chars >= 0x80
 
22
static inline bool isspacechar(unsigned char ch) {
 
23
        return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
 
24
}
 
25
 
 
26
static inline bool IsPunctuation(char ch) {
 
27
        return isascii(ch) && ispunct(ch);
 
28
}
 
29
 
 
30
static inline bool IsADigit(char ch) {
 
31
        return isascii(ch) && isdigit(ch);
 
32
}
 
33
 
 
34
static inline bool IsLowerCase(char ch) {
 
35
        return isascii(ch) && islower(ch);
 
36
}
 
37
 
 
38
static inline bool IsUpperCase(char ch) {
 
39
        return isascii(ch) && isupper(ch);
 
40
}
 
41
 
 
42
Document::Document() {
 
43
        refCount = 0;
 
44
#ifdef unix
 
45
        eolMode = SC_EOL_LF;
 
46
#else
 
47
        eolMode = SC_EOL_CRLF;
 
48
#endif
 
49
        dbcsCodePage = 0;
 
50
        stylingBits = 5;
 
51
        stylingBitsMask = 0x1F;
 
52
        stylingMask = 0;
 
53
        SetDefaultCharClasses(true);
 
54
        endStyled = 0;
 
55
        styleClock = 0;
 
56
        enteredCount = 0;
 
57
        enteredReadOnlyCount = 0;
 
58
        tabInChars = 8;
 
59
        indentInChars = 0;
 
60
        actualIndentInChars = 8;
 
61
        useTabs = true;
 
62
        tabIndents = true;
 
63
        backspaceUnindents = false;
 
64
        watchers = 0;
 
65
        lenWatchers = 0;
 
66
 
 
67
        matchesValid = false;
 
68
        pre = 0;
 
69
        substituted = 0;
 
70
}
 
71
 
 
72
Document::~Document() {
 
73
        for (int i = 0; i < lenWatchers; i++) {
 
74
                watchers[i].watcher->NotifyDeleted(this, watchers[i].userData);
 
75
        }
 
76
        delete []watchers;
 
77
        watchers = 0;
 
78
        lenWatchers = 0;
 
79
        delete pre;
 
80
        pre = 0;
 
81
        delete []substituted;
 
82
        substituted = 0;
 
83
}
 
84
 
 
85
// Increase reference count and return its previous value.
 
86
int Document::AddRef() {
 
87
        return refCount++;
 
88
}
 
89
 
 
90
// Decrease reference count and return its previous value.
 
91
// Delete the document if reference count reaches zero.
 
92
int Document::Release() {
 
93
        int curRefCount = --refCount;
 
94
        if (curRefCount == 0)
 
95
                delete this;
 
96
        return curRefCount;
 
97
}
 
98
 
 
99
void Document::SetSavePoint() {
 
100
        cb.SetSavePoint();
 
101
        NotifySavePoint(true);
 
102
}
 
103
 
 
104
int Document::AddMark(int line, int markerNum) {
 
105
        int prev = cb.AddMark(line, markerNum);
 
106
        DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
 
107
        mh.line = line;
 
108
        NotifyModified(mh);
 
109
        return prev;
 
110
}
 
111
 
 
112
void Document::AddMarkSet(int line, int valueSet) {
 
113
        unsigned int m = valueSet;
 
114
        for (int i = 0; m; i++, m >>= 1)
 
115
                if (m & 1)
 
116
                        cb.AddMark(line, i);
 
117
        DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
 
118
        mh.line = line;
 
119
        NotifyModified(mh);
 
120
}
 
121
 
 
122
void Document::DeleteMark(int line, int markerNum) {
 
123
        cb.DeleteMark(line, markerNum);
 
124
        DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
 
125
        mh.line = line;
 
126
        NotifyModified(mh);
 
127
}
 
128
 
 
129
void Document::DeleteMarkFromHandle(int markerHandle) {
 
130
        cb.DeleteMarkFromHandle(markerHandle);
 
131
        DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
 
132
        mh.line = -1;
 
133
        NotifyModified(mh);
 
134
}
 
135
 
 
136
void Document::DeleteAllMarks(int markerNum) {
 
137
        cb.DeleteAllMarks(markerNum);
 
138
        DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
 
139
        mh.line = -1;
 
140
        NotifyModified(mh);
 
141
}
 
142
 
 
143
int Document::LineStart(int line) {
 
144
        return cb.LineStart(line);
 
145
}
 
146
 
 
147
int Document::LineEnd(int line) {
 
148
        if (line == LinesTotal() - 1) {
 
149
                return LineStart(line + 1);
 
150
        } else {
 
151
                int position = LineStart(line + 1) - 1;
 
152
                // When line terminator is CR+LF, may need to go back one more
 
153
                if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) {
 
154
                        position--;
 
155
                }
 
156
                return position;
 
157
        }
 
158
}
 
159
 
 
160
int Document::LineFromPosition(int pos) {
 
161
        return cb.LineFromPosition(pos);
 
162
}
 
163
 
 
164
int Document::LineEndPosition(int position) {
 
165
        return LineEnd(LineFromPosition(position));
 
166
}
 
167
 
 
168
int Document::VCHomePosition(int position) {
 
169
        int line = LineFromPosition(position);
 
170
        int startPosition = LineStart(line);
 
171
        int endLine = LineStart(line + 1) - 1;
 
172
        int startText = startPosition;
 
173
        while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) )
 
174
                startText++;
 
175
        if (position == startText)
 
176
                return startPosition;
 
177
        else
 
178
                return startText;
 
179
}
 
180
 
 
181
int Document::SetLevel(int line, int level) {
 
182
        int prev = cb.SetLevel(line, level);
 
183
        if (prev != level) {
 
184
                DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
 
185
                                   LineStart(line), 0, 0, 0);
 
186
                mh.line = line;
 
187
                mh.foldLevelNow = level;
 
188
                mh.foldLevelPrev = prev;
 
189
                NotifyModified(mh);
 
190
        }
 
191
        return prev;
 
192
}
 
193
 
 
194
static bool IsSubordinate(int levelStart, int levelTry) {
 
195
        if (levelTry & SC_FOLDLEVELWHITEFLAG)
 
196
                return true;
 
197
        else
 
198
                return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
 
199
}
 
200
 
 
201
int Document::GetLastChild(int lineParent, int level) {
 
202
        if (level == -1)
 
203
                level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK;
 
204
        int maxLine = LinesTotal();
 
205
        int lineMaxSubord = lineParent;
 
206
        while (lineMaxSubord < maxLine - 1) {
 
207
                EnsureStyledTo(LineStart(lineMaxSubord + 2));
 
208
                if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))
 
209
                        break;
 
210
                lineMaxSubord++;
 
211
        }
 
212
        if (lineMaxSubord > lineParent) {
 
213
                if (level > (GetLevel(lineMaxSubord + 1) & SC_FOLDLEVELNUMBERMASK)) {
 
214
                        // Have chewed up some whitespace that belongs to a parent so seek back
 
215
                        if (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG) {
 
216
                                lineMaxSubord--;
 
217
                        }
 
218
                }
 
219
        }
 
220
        return lineMaxSubord;
 
221
}
 
222
 
 
223
int Document::GetFoldParent(int line) {
 
224
        int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK;
 
225
        int lineLook = line - 1;
 
226
        while ((lineLook > 0) && (
 
227
                    (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
 
228
                    ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
 
229
              ) {
 
230
                lineLook--;
 
231
        }
 
232
        if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
 
233
                ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
 
234
                return lineLook;
 
235
        } else {
 
236
                return -1;
 
237
        }
 
238
}
 
239
 
 
240
int Document::ClampPositionIntoDocument(int pos) {
 
241
        return Platform::Clamp(pos, 0, Length());
 
242
}
 
243
 
 
244
bool Document::IsCrLf(int pos) {
 
245
        if (pos < 0)
 
246
                return false;
 
247
        if (pos >= (Length() - 1))
 
248
                return false;
 
249
        return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n');
 
250
}
 
251
 
 
252
static const int maxBytesInDBCSCharacter=5;
 
253
 
 
254
int Document::LenChar(int pos) {
 
255
        if (pos < 0) {
 
256
                return 1;
 
257
        } else if (IsCrLf(pos)) {
 
258
                return 2;
 
259
        } else if (SC_CP_UTF8 == dbcsCodePage) {
 
260
                unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
 
261
                if (ch < 0x80)
 
262
                        return 1;
 
263
                int len = 2;
 
264
                if (ch >= (0x80 + 0x40 + 0x20))
 
265
                        len = 3;
 
266
                int lengthDoc = Length();
 
267
                if ((pos + len) > lengthDoc)
 
268
                        return lengthDoc -pos;
 
269
                else
 
270
                        return len;
 
271
        } else if (dbcsCodePage) {
 
272
                char mbstr[maxBytesInDBCSCharacter+1];
 
273
                int i;
 
274
                for (i=0; i<Platform::DBCSCharMaxLength(); i++) {
 
275
                        mbstr[i] = cb.CharAt(pos+i);
 
276
                }
 
277
                mbstr[i] = '\0';
 
278
                return Platform::DBCSCharLength(dbcsCodePage, mbstr);
 
279
        } else {
 
280
                return 1;
 
281
        }
 
282
}
 
283
 
 
284
// Normalise a position so that it is not halfway through a two byte character.
 
285
// This can occur in two situations -
 
286
// When lines are terminated with \r\n pairs which should be treated as one character.
 
287
// When displaying DBCS text such as Japanese.
 
288
// If moving, move the position in the indicated direction.
 
289
int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
 
290
        //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
 
291
        // If out of range, just return minimum/maximum value.
 
292
        if (pos <= 0)
 
293
                return 0;
 
294
        if (pos >= Length())
 
295
                return Length();
 
296
 
 
297
        // PLATFORM_ASSERT(pos > 0 && pos < Length());
 
298
        if (checkLineEnd && IsCrLf(pos - 1)) {
 
299
                if (moveDir > 0)
 
300
                        return pos + 1;
 
301
                else
 
302
                        return pos - 1;
 
303
        }
 
304
 
 
305
        // Not between CR and LF
 
306
 
 
307
        if (dbcsCodePage) {
 
308
                if (SC_CP_UTF8 == dbcsCodePage) {
 
309
                        unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos));
 
310
                        while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) {
 
311
                                // ch is a trail byte
 
312
                                if (moveDir > 0)
 
313
                                        pos++;
 
314
                                else
 
315
                                        pos--;
 
316
                                ch = static_cast<unsigned char>(cb.CharAt(pos));
 
317
                        }
 
318
                } else {
 
319
                        // Anchor DBCS calculations at start of line because start of line can
 
320
                        // not be a DBCS trail byte.
 
321
                        int posCheck = LineStart(LineFromPosition(pos));
 
322
                        while (posCheck < pos) {
 
323
                                char mbstr[maxBytesInDBCSCharacter+1];
 
324
                                int i;
 
325
                                for(i=0;i<Platform::DBCSCharMaxLength();i++) {
 
326
                                        mbstr[i] = cb.CharAt(posCheck+i);
 
327
                                }
 
328
                                mbstr[i] = '\0';
 
329
 
 
330
                                int mbsize = Platform::DBCSCharLength(dbcsCodePage, mbstr);
 
331
                                if (posCheck + mbsize == pos) {
 
332
                                        return pos;
 
333
                                } else if (posCheck + mbsize > pos) {
 
334
                                        if (moveDir > 0) {
 
335
                                                return posCheck + mbsize;
 
336
                                        } else {
 
337
                                                return posCheck;
 
338
                                        }
 
339
                                }
 
340
                                posCheck += mbsize;
 
341
                        }
 
342
                }
 
343
        }
 
344
 
 
345
        return pos;
 
346
}
 
347
 
 
348
void Document::ModifiedAt(int pos) {
 
349
        if (endStyled > pos)
 
350
                endStyled = pos;
 
351
}
 
352
 
 
353
void Document::CheckReadOnly() {
 
354
        if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
 
355
                enteredReadOnlyCount++;
 
356
                NotifyModifyAttempt();
 
357
                enteredReadOnlyCount--;
 
358
        }
 
359
}
 
360
 
 
361
// Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
 
362
// SetStyleAt does not change the persistent state of a document
 
363
 
 
364
// Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
 
365
bool Document::DeleteChars(int pos, int len) {
 
366
        if (len == 0)
 
367
                return false;
 
368
        if ((pos + len) > Length())
 
369
                return false;
 
370
        CheckReadOnly();
 
371
        if (enteredCount != 0) {
 
372
                return false;
 
373
        } else {
 
374
                enteredCount++;
 
375
                if (!cb.IsReadOnly()) {
 
376
                        NotifyModified(
 
377
                            DocModification(
 
378
                                SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
 
379
                                pos, len,
 
380
                                0, 0));
 
381
                        int prevLinesTotal = LinesTotal();
 
382
                        bool startSavePoint = cb.IsSavePoint();
 
383
                        const char *text = cb.DeleteChars(pos * 2, len * 2);
 
384
                        if (startSavePoint && cb.IsCollectingUndo())
 
385
                                NotifySavePoint(!startSavePoint);
 
386
                        if ((pos < Length()) || (pos == 0))
 
387
                                ModifiedAt(pos);
 
388
                        else
 
389
                                ModifiedAt(pos-1);
 
390
                        NotifyModified(
 
391
                            DocModification(
 
392
                                SC_MOD_DELETETEXT | SC_PERFORMED_USER,
 
393
                                pos, len,
 
394
                                LinesTotal() - prevLinesTotal, text));
 
395
                }
 
396
                enteredCount--;
 
397
        }
 
398
        return !cb.IsReadOnly();
 
399
}
 
400
 
 
401
/**
 
402
 * Insert a styled string (char/style pairs) with a length.
 
403
 */
 
404
bool Document::InsertStyledString(int position, char *s, int insertLength) {
 
405
        CheckReadOnly();
 
406
        if (enteredCount != 0) {
 
407
                return false;
 
408
        } else {
 
409
                enteredCount++;
 
410
                if (!cb.IsReadOnly()) {
 
411
                        NotifyModified(
 
412
                            DocModification(
 
413
                                SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
 
414
                                position / 2, insertLength / 2,
 
415
                                0, s));
 
416
                        int prevLinesTotal = LinesTotal();
 
417
                        bool startSavePoint = cb.IsSavePoint();
 
418
                        const char *text = cb.InsertString(position, s, insertLength);
 
419
                        if (startSavePoint && cb.IsCollectingUndo())
 
420
                                NotifySavePoint(!startSavePoint);
 
421
                        ModifiedAt(position / 2);
 
422
                        NotifyModified(
 
423
                            DocModification(
 
424
                                SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
 
425
                                position / 2, insertLength / 2,
 
426
                                LinesTotal() - prevLinesTotal, text));
 
427
                }
 
428
                enteredCount--;
 
429
        }
 
430
        return !cb.IsReadOnly();
 
431
}
 
432
 
 
433
int Document::Undo() {
 
434
        int newPos = -1;
 
435
        CheckReadOnly();
 
436
        if (enteredCount == 0) {
 
437
                enteredCount++;
 
438
                if (!cb.IsReadOnly()) {
 
439
                        bool startSavePoint = cb.IsSavePoint();
 
440
                        bool multiLine = false;
 
441
                        int steps = cb.StartUndo();
 
442
                        //Platform::DebugPrintf("Steps=%d\n", steps);
 
443
                        for (int step = 0; step < steps; step++) {
 
444
                                const int prevLinesTotal = LinesTotal();
 
445
                                const Action &action = cb.GetUndoStep();
 
446
                                if (action.at == removeAction) {
 
447
                                        NotifyModified(DocModification(
 
448
                                                                        SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action));
 
449
                                } else {
 
450
                                        NotifyModified(DocModification(
 
451
                                                                        SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action));
 
452
                                }
 
453
                                cb.PerformUndoStep();
 
454
                                int cellPosition = action.position;
 
455
                                ModifiedAt(cellPosition);
 
456
                                newPos = cellPosition;
 
457
 
 
458
                                int modFlags = SC_PERFORMED_UNDO;
 
459
                                // With undo, an insertion action becomes a deletion notification
 
460
                                if (action.at == removeAction) {
 
461
                                        newPos += action.lenData;
 
462
                                        modFlags |= SC_MOD_INSERTTEXT;
 
463
                                } else {
 
464
                                        modFlags |= SC_MOD_DELETETEXT;
 
465
                                }
 
466
                                if (steps > 1)
 
467
                                        modFlags |= SC_MULTISTEPUNDOREDO;
 
468
                                const int linesAdded = LinesTotal() - prevLinesTotal;
 
469
                                if (linesAdded != 0)
 
470
                                        multiLine = true;
 
471
                                if (step == steps - 1) {
 
472
                                        modFlags |= SC_LASTSTEPINUNDOREDO;
 
473
                                        if (multiLine)
 
474
                                                modFlags |= SC_MULTILINEUNDOREDO;
 
475
                                }
 
476
                                NotifyModified(DocModification(modFlags, cellPosition, action.lenData,
 
477
                                                                                           linesAdded, action.data));
 
478
                        }
 
479
 
 
480
                        bool endSavePoint = cb.IsSavePoint();
 
481
                        if (startSavePoint != endSavePoint)
 
482
                                NotifySavePoint(endSavePoint);
 
483
                }
 
484
                enteredCount--;
 
485
        }
 
486
        return newPos;
 
487
}
 
488
 
 
489
int Document::Redo() {
 
490
        int newPos = -1;
 
491
        CheckReadOnly();
 
492
        if (enteredCount == 0) {
 
493
                enteredCount++;
 
494
                if (!cb.IsReadOnly()) {
 
495
                        bool startSavePoint = cb.IsSavePoint();
 
496
                        bool multiLine = false;
 
497
                        int steps = cb.StartRedo();
 
498
                        for (int step = 0; step < steps; step++) {
 
499
                                const int prevLinesTotal = LinesTotal();
 
500
                                const Action &action = cb.GetRedoStep();
 
501
                                if (action.at == insertAction) {
 
502
                                        NotifyModified(DocModification(
 
503
                                                                        SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action));
 
504
                                } else {
 
505
                                        NotifyModified(DocModification(
 
506
                                                                        SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action));
 
507
                                }
 
508
                                cb.PerformRedoStep();
 
509
                                ModifiedAt(action.position);
 
510
                                newPos = action.position;
 
511
 
 
512
                                int modFlags = SC_PERFORMED_REDO;
 
513
                                if (action.at == insertAction) {
 
514
                                        newPos += action.lenData;
 
515
                                        modFlags |= SC_MOD_INSERTTEXT;
 
516
                                } else {
 
517
                                        modFlags |= SC_MOD_DELETETEXT;
 
518
                                }
 
519
                                if (steps > 1)
 
520
                                        modFlags |= SC_MULTISTEPUNDOREDO;
 
521
                                const int linesAdded = LinesTotal() - prevLinesTotal;
 
522
                                if (linesAdded != 0)
 
523
                                        multiLine = true;
 
524
                                if (step == steps - 1) {
 
525
                                        modFlags |= SC_LASTSTEPINUNDOREDO;
 
526
                                        if (multiLine)
 
527
                                                modFlags |= SC_MULTILINEUNDOREDO;
 
528
                                }
 
529
                                NotifyModified(
 
530
                                        DocModification(modFlags, action.position, action.lenData,
 
531
                                                                        linesAdded, action.data));
 
532
                        }
 
533
 
 
534
                        bool endSavePoint = cb.IsSavePoint();
 
535
                        if (startSavePoint != endSavePoint)
 
536
                                NotifySavePoint(endSavePoint);
 
537
                }
 
538
                enteredCount--;
 
539
        }
 
540
        return newPos;
 
541
}
 
542
 
 
543
/**
 
544
 * Insert a single character.
 
545
 */
 
546
bool Document::InsertChar(int pos, char ch) {
 
547
        char chs[2];
 
548
        chs[0] = ch;
 
549
        chs[1] = 0;
 
550
        return InsertStyledString(pos*2, chs, 2);
 
551
}
 
552
 
 
553
/**
 
554
 * Insert a null terminated string.
 
555
 */
 
556
bool Document::InsertString(int position, const char *s) {
 
557
        return InsertString(position, s, strlen(s));
 
558
}
 
559
 
 
560
/**
 
561
 * Insert a string with a length.
 
562
 */
 
563
bool Document::InsertString(int position, const char *s, size_t insertLength) {
 
564
        bool changed = false;
 
565
        if (insertLength > 0) {
 
566
                char *sWithStyle = new char[insertLength * 2];
 
567
                if (sWithStyle) {
 
568
                        for (size_t i = 0; i < insertLength; i++) {
 
569
                                sWithStyle[i*2] = s[i];
 
570
                                sWithStyle[i*2 + 1] = 0;
 
571
                        }
 
572
                        changed = InsertStyledString(position*2, sWithStyle,
 
573
                                static_cast<int>(insertLength*2));
 
574
                        delete []sWithStyle;
 
575
                }
 
576
        }
 
577
        return changed;
 
578
}
 
579
 
 
580
void Document::ChangeChar(int pos, char ch) {
 
581
        DeleteChars(pos, 1);
 
582
        InsertChar(pos, ch);
 
583
}
 
584
 
 
585
void Document::DelChar(int pos) {
 
586
        DeleteChars(pos, LenChar(pos));
 
587
}
 
588
 
 
589
void Document::DelCharBack(int pos) {
 
590
        if (pos <= 0) {
 
591
                return;
 
592
        } else if (IsCrLf(pos - 2)) {
 
593
                DeleteChars(pos - 2, 2);
 
594
        } else if (dbcsCodePage) {
 
595
                int startChar = MovePositionOutsideChar(pos - 1, -1, false);
 
596
                DeleteChars(startChar, pos - startChar);
 
597
        } else {
 
598
                DeleteChars(pos - 1, 1);
 
599
        }
 
600
}
 
601
 
 
602
static bool isindentchar(char ch) {
 
603
        return (ch == ' ') || (ch == '\t');
 
604
}
 
605
 
 
606
static int NextTab(int pos, int tabSize) {
 
607
        return ((pos / tabSize) + 1) * tabSize;
 
608
}
 
609
 
 
610
static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) {
 
611
        length--;       // ensure space for \0
 
612
        if (!insertSpaces) {
 
613
                while ((indent >= tabSize) && (length > 0)) {
 
614
                        *linebuf++ = '\t';
 
615
                        indent -= tabSize;
 
616
                        length--;
 
617
                }
 
618
        }
 
619
        while ((indent > 0) && (length > 0)) {
 
620
                *linebuf++ = ' ';
 
621
                indent--;
 
622
                length--;
 
623
        }
 
624
        *linebuf = '\0';
 
625
}
 
626
 
 
627
int Document::GetLineIndentation(int line) {
 
628
        int indent = 0;
 
629
        if ((line >= 0) && (line < LinesTotal())) {
 
630
                int lineStart = LineStart(line);
 
631
                int length = Length();
 
632
                for (int i = lineStart;i < length;i++) {
 
633
                        char ch = cb.CharAt(i);
 
634
                        if (ch == ' ')
 
635
                                indent++;
 
636
                        else if (ch == '\t')
 
637
                                indent = NextTab(indent, tabInChars);
 
638
                        else
 
639
                                return indent;
 
640
                }
 
641
        }
 
642
        return indent;
 
643
}
 
644
 
 
645
void Document::SetLineIndentation(int line, int indent) {
 
646
        int indentOfLine = GetLineIndentation(line);
 
647
        if (indent < 0)
 
648
                indent = 0;
 
649
        if (indent != indentOfLine) {
 
650
                char linebuf[1000];
 
651
                CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs);
 
652
                int thisLineStart = LineStart(line);
 
653
                int indentPos = GetLineIndentPosition(line);
 
654
                BeginUndoAction();
 
655
                DeleteChars(thisLineStart, indentPos - thisLineStart);
 
656
                InsertString(thisLineStart, linebuf);
 
657
                EndUndoAction();
 
658
        }
 
659
}
 
660
 
 
661
int Document::GetLineIndentPosition(int line) {
 
662
        if (line < 0)
 
663
                return 0;
 
664
        int pos = LineStart(line);
 
665
        int length = Length();
 
666
        while ((pos < length) && isindentchar(cb.CharAt(pos))) {
 
667
                pos++;
 
668
        }
 
669
        return pos;
 
670
}
 
671
 
 
672
int Document::GetColumn(int pos) {
 
673
        int column = 0;
 
674
        int line = LineFromPosition(pos);
 
675
        if ((line >= 0) && (line < LinesTotal())) {
 
676
                for (int i = LineStart(line);i < pos;) {
 
677
                        char ch = cb.CharAt(i);
 
678
                        if (ch == '\t') {
 
679
                                column = NextTab(column, tabInChars);
 
680
                                i++;
 
681
                        } else if (ch == '\r') {
 
682
                                return column;
 
683
                        } else if (ch == '\n') {
 
684
                                return column;
 
685
                        } else {
 
686
                                column++;
 
687
                                i = MovePositionOutsideChar(i + 1, 1);
 
688
                        }
 
689
                }
 
690
        }
 
691
        return column;
 
692
}
 
693
 
 
694
int Document::FindColumn(int line, int column) {
 
695
        int position = LineStart(line);
 
696
        int columnCurrent = 0;
 
697
        if ((line >= 0) && (line < LinesTotal())) {
 
698
                while ((columnCurrent < column) && (position < Length())) {
 
699
                        char ch = cb.CharAt(position);
 
700
                        if (ch == '\t') {
 
701
                                columnCurrent = NextTab(columnCurrent, tabInChars);
 
702
                                position++;
 
703
                        } else if (ch == '\r') {
 
704
                                return position;
 
705
                        } else if (ch == '\n') {
 
706
                                return position;
 
707
                        } else {
 
708
                                columnCurrent++;
 
709
                                position = MovePositionOutsideChar(position + 1, 1);
 
710
                        }
 
711
                }
 
712
        }
 
713
        return position;
 
714
}
 
715
 
 
716
void Document::Indent(bool forwards, int lineBottom, int lineTop) {
 
717
        // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
 
718
        for (int line = lineBottom; line >= lineTop; line--) {
 
719
                int indentOfLine = GetLineIndentation(line);
 
720
                if (forwards) {
 
721
                        if (LineStart(line) < LineEnd(line)) {
 
722
                                SetLineIndentation(line, indentOfLine + IndentSize());
 
723
                        }
 
724
                } else {
 
725
                        SetLineIndentation(line, indentOfLine - IndentSize());
 
726
                }
 
727
        }
 
728
}
 
729
 
 
730
// Convert line endings for a piece of text to a particular mode.
 
731
// Stop at len or when a NUL is found.
 
732
// Caller must delete the returned pointer.
 
733
char *Document::TransformLineEnds(int *pLenOut, const char *s, size_t len, int eolMode) {
 
734
        char *dest = new char[2 * len + 1];
 
735
        const char *sptr = s;
 
736
        char *dptr = dest;
 
737
        for (size_t i = 0; (i < len) && (*sptr != '\0'); i++) {
 
738
                if (*sptr == '\n' || *sptr == '\r') {
 
739
                        if (eolMode == SC_EOL_CR) {
 
740
                                *dptr++ = '\r';
 
741
                        } else if (eolMode == SC_EOL_LF) {
 
742
                                *dptr++ = '\n';
 
743
                        } else { // eolMode == SC_EOL_CRLF
 
744
                                *dptr++ = '\r';
 
745
                                *dptr++ = '\n';
 
746
                        }
 
747
                        if ((*sptr == '\r') && (i+1 < len) && (*(sptr+1) == '\n')) {
 
748
                                i++;
 
749
                                sptr++;
 
750
                        }
 
751
                        sptr++;
 
752
                } else {
 
753
                        *dptr++ = *sptr++;
 
754
                }
 
755
        }
 
756
        *dptr++ = '\0';
 
757
        *pLenOut = (dptr - dest) - 1;
 
758
        return dest;
 
759
}
 
760
 
 
761
void Document::ConvertLineEnds(int eolModeSet) {
 
762
        BeginUndoAction();
 
763
 
 
764
        for (int pos = 0; pos < Length(); pos++) {
 
765
                if (cb.CharAt(pos) == '\r') {
 
766
                        if (cb.CharAt(pos + 1) == '\n') {
 
767
                                // CRLF
 
768
                                if (eolModeSet == SC_EOL_CR) {
 
769
                                        DeleteChars(pos + 1, 1); // Delete the LF
 
770
                                } else if (eolModeSet == SC_EOL_LF) {
 
771
                                        DeleteChars(pos, 1); // Delete the CR
 
772
                                } else {
 
773
                                        pos++;
 
774
                                }
 
775
                        } else {
 
776
                                // CR
 
777
                                if (eolModeSet == SC_EOL_CRLF) {
 
778
                                        InsertString(pos + 1, "\n", 1); // Insert LF
 
779
                                        pos++;
 
780
                                } else if (eolModeSet == SC_EOL_LF) {
 
781
                                        InsertString(pos, "\n", 1); // Insert LF
 
782
                                        DeleteChars(pos + 1, 1); // Delete CR
 
783
                                }
 
784
                        }
 
785
                } else if (cb.CharAt(pos) == '\n') {
 
786
                        // LF
 
787
                        if (eolModeSet == SC_EOL_CRLF) {
 
788
                                InsertString(pos, "\r", 1); // Insert CR
 
789
                                pos++;
 
790
                        } else if (eolModeSet == SC_EOL_CR) {
 
791
                                InsertString(pos, "\r", 1); // Insert CR
 
792
                                DeleteChars(pos + 1, 1); // Delete LF
 
793
                        }
 
794
                }
 
795
        }
 
796
 
 
797
        EndUndoAction();
 
798
}
 
799
 
 
800
bool Document::IsWhiteLine(int line) {
 
801
        int currentChar = LineStart(line);
 
802
        int endLine = LineEnd(line);
 
803
        while (currentChar < endLine) {
 
804
                if (cb.CharAt(currentChar) != ' ' && cb.CharAt(currentChar) != '\t') {
 
805
                        return false;
 
806
                }
 
807
                ++currentChar;
 
808
        }
 
809
        return true;
 
810
}
 
811
 
 
812
int Document::ParaUp(int pos) {
 
813
        int line = LineFromPosition(pos);
 
814
        line--;
 
815
        while (line >= 0 && IsWhiteLine(line)) { // skip empty lines
 
816
                line--;
 
817
        }
 
818
        while (line >= 0 && !IsWhiteLine(line)) { // skip non-empty lines
 
819
                line--;
 
820
        }
 
821
        line++;
 
822
        return LineStart(line);
 
823
}
 
824
 
 
825
int Document::ParaDown(int pos) {
 
826
        int line = LineFromPosition(pos);
 
827
        while (line < LinesTotal() && !IsWhiteLine(line)) { // skip non-empty lines
 
828
                line++;
 
829
        }
 
830
        while (line < LinesTotal() && IsWhiteLine(line)) { // skip empty lines
 
831
                line++;
 
832
        }
 
833
        if (line < LinesTotal())
 
834
                return LineStart(line);
 
835
        else // end of a document
 
836
                return LineEnd(line-1);
 
837
}
 
838
 
 
839
Document::charClassification Document::WordCharClass(unsigned char ch) {
 
840
        if ((SC_CP_UTF8 == dbcsCodePage) && (ch >= 0x80))
 
841
                return ccWord;
 
842
        return charClass[ch];
 
843
}
 
844
 
 
845
/**
 
846
 * Used by commmands that want to select whole words.
 
847
 * Finds the start of word at pos when delta < 0 or the end of the word when delta >= 0.
 
848
 */
 
849
int Document::ExtendWordSelect(int pos, int delta, bool onlyWordCharacters) {
 
850
        charClassification ccStart = ccWord;
 
851
        if (delta < 0) {
 
852
                if (!onlyWordCharacters)
 
853
                        ccStart = WordCharClass(cb.CharAt(pos-1));
 
854
                while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart))
 
855
                        pos--;
 
856
        } else {
 
857
                if (!onlyWordCharacters)
 
858
                        ccStart = WordCharClass(cb.CharAt(pos));
 
859
                while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
 
860
                        pos++;
 
861
        }
 
862
        return MovePositionOutsideChar(pos, delta);
 
863
}
 
864
 
 
865
/**
 
866
 * Find the start of the next word in either a forward (delta >= 0) or backwards direction
 
867
 * (delta < 0).
 
868
 * This is looking for a transition between character classes although there is also some
 
869
 * additional movement to transit white space.
 
870
 * Used by cursor movement by word commands.
 
871
 */
 
872
int Document::NextWordStart(int pos, int delta) {
 
873
        if (delta < 0) {
 
874
                while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccSpace))
 
875
                        pos--;
 
876
                if (pos > 0) {
 
877
                        charClassification ccStart = WordCharClass(cb.CharAt(pos-1));
 
878
                        while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) {
 
879
                                pos--;
 
880
                        }
 
881
                }
 
882
        } else {
 
883
                charClassification ccStart = WordCharClass(cb.CharAt(pos));
 
884
                while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
 
885
                        pos++;
 
886
                while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccSpace))
 
887
                        pos++;
 
888
        }
 
889
        return pos;
 
890
}
 
891
 
 
892
/**
 
893
 * Find the end of the next word in either a forward (delta >= 0) or backwards direction
 
894
 * (delta < 0).
 
895
 * This is looking for a transition between character classes although there is also some
 
896
 * additional movement to transit white space.
 
897
 * Used by cursor movement by word commands.
 
898
 */
 
899
int Document::NextWordEnd(int pos, int delta) {
 
900
        if (delta < 0) {
 
901
                if (pos > 0) {
 
902
                        charClassification ccStart = WordCharClass(cb.CharAt(pos-1));
 
903
                        if (ccStart != ccSpace) {
 
904
                                while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) {
 
905
                                        pos--;
 
906
                                }
 
907
                        }
 
908
                        while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccSpace) {
 
909
                                pos--;
 
910
                        }
 
911
                }
 
912
        } else {
 
913
                while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccSpace) {
 
914
                        pos++;
 
915
                }
 
916
                if (pos < Length()) {
 
917
                        charClassification ccStart = WordCharClass(cb.CharAt(pos));
 
918
                        while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) {
 
919
                                pos++;
 
920
                        }
 
921
                }
 
922
        }
 
923
        return pos;
 
924
}
 
925
 
 
926
/**
 
927
 * Check that the character at the given position is a word or punctuation character and that
 
928
 * the previous character is of a different character class.
 
929
 */
 
930
bool Document::IsWordStartAt(int pos) {
 
931
        if (pos > 0) {
 
932
                charClassification ccPos = WordCharClass(CharAt(pos));
 
933
                return (ccPos == ccWord || ccPos == ccPunctuation) &&
 
934
                        (ccPos != WordCharClass(CharAt(pos - 1)));
 
935
        }
 
936
        return true;
 
937
}
 
938
 
 
939
/**
 
940
 * Check that the character at the given position is a word or punctuation character and that
 
941
 * the next character is of a different character class.
 
942
 */
 
943
bool Document::IsWordEndAt(int pos) {
 
944
        if (pos < Length()) {
 
945
                charClassification ccPrev = WordCharClass(CharAt(pos-1));
 
946
                return (ccPrev == ccWord || ccPrev == ccPunctuation) &&
 
947
                        (ccPrev != WordCharClass(CharAt(pos)));
 
948
        }
 
949
        return true;
 
950
}
 
951
 
 
952
/**
 
953
 * Check that the given range is has transitions between character classes at both
 
954
 * ends and where the characters on the inside are word or punctuation characters.
 
955
 */
 
956
bool Document::IsWordAt(int start, int end) {
 
957
        return IsWordStartAt(start) && IsWordEndAt(end);
 
958
}
 
959
 
 
960
// The comparison and case changing functions here assume ASCII
 
961
// or extended ASCII such as the normal Windows code page.
 
962
 
 
963
static inline char MakeUpperCase(char ch) {
 
964
        if (ch < 'a' || ch > 'z')
 
965
                return ch;
 
966
        else
 
967
                return static_cast<char>(ch - 'a' + 'A');
 
968
}
 
969
 
 
970
static inline char MakeLowerCase(char ch) {
 
971
        if (ch < 'A' || ch > 'Z')
 
972
                return ch;
 
973
        else
 
974
                return static_cast<char>(ch - 'A' + 'a');
 
975
}
 
976
 
 
977
// Define a way for the Regular Expression code to access the document
 
978
class DocumentIndexer : public CharacterIndexer {
 
979
        Document *pdoc;
 
980
        int end;
 
981
public:
 
982
        DocumentIndexer(Document *pdoc_, int end_) :
 
983
                pdoc(pdoc_), end(end_) {
 
984
        }
 
985
 
 
986
        virtual ~DocumentIndexer() {
 
987
        }
 
988
 
 
989
        virtual char CharAt(int index) {
 
990
                if (index < 0 || index >= end)
 
991
                        return 0;
 
992
                else
 
993
                        return pdoc->CharAt(index);
 
994
        }
 
995
};
 
996
 
 
997
/**
 
998
 * Find text in document, supporting both forward and backward
 
999
 * searches (just pass minPos > maxPos to do a backward search)
 
1000
 * Has not been tested with backwards DBCS searches yet.
 
1001
 */
 
1002
long Document::FindText(int minPos, int maxPos, const char *s,
 
1003
                        bool caseSensitive, bool word, bool wordStart, bool regExp, bool posix,
 
1004
                        int *length) {
 
1005
        if (regExp) {
 
1006
                if (!pre)
 
1007
                        pre = new RESearch();
 
1008
                if (!pre)
 
1009
                        return -1;
 
1010
 
 
1011
                int increment = (minPos <= maxPos) ? 1 : -1;
 
1012
 
 
1013
                int startPos = minPos;
 
1014
                int endPos = maxPos;
 
1015
 
 
1016
                // Range endpoints should not be inside DBCS characters, but just in case, move them.
 
1017
                startPos = MovePositionOutsideChar(startPos, 1, false);
 
1018
                endPos = MovePositionOutsideChar(endPos, 1, false);
 
1019
 
 
1020
                const char *errmsg = pre->Compile(s, *length, caseSensitive, posix);
 
1021
                if (errmsg) {
 
1022
                        return -1;
 
1023
                }
 
1024
                // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\))
 
1025
                // Replace first '.' with '-' in each property file variable reference:
 
1026
                //     Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\))
 
1027
                //     Replace: $(\1-\2)
 
1028
                int lineRangeStart = LineFromPosition(startPos);
 
1029
                int lineRangeEnd = LineFromPosition(endPos);
 
1030
                if ((increment == 1) &&
 
1031
                        (startPos >= LineEnd(lineRangeStart)) &&
 
1032
                        (lineRangeStart < lineRangeEnd)) {
 
1033
                        // the start position is at end of line or between line end characters.
 
1034
                        lineRangeStart++;
 
1035
                        startPos = LineStart(lineRangeStart);
 
1036
                }
 
1037
                int pos = -1;
 
1038
                int lenRet = 0;
 
1039
                char searchEnd = s[*length - 1];
 
1040
                int lineRangeBreak = lineRangeEnd + increment;
 
1041
                for (int line = lineRangeStart; line != lineRangeBreak; line += increment) {
 
1042
                        int startOfLine = LineStart(line);
 
1043
                        int endOfLine = LineEnd(line);
 
1044
                        if (increment == 1) {
 
1045
                                if (line == lineRangeStart) {
 
1046
                                        if ((startPos != startOfLine) && (s[0] == '^'))
 
1047
                                                continue;       // Can't match start of line if start position after start of line
 
1048
                                        startOfLine = startPos;
 
1049
                                }
 
1050
                                if (line == lineRangeEnd) {
 
1051
                                        if ((endPos != endOfLine) && (searchEnd == '$'))
 
1052
                                                continue;       // Can't match end of line if end position before end of line
 
1053
                                        endOfLine = endPos;
 
1054
                                }
 
1055
                        } else {
 
1056
                                if (line == lineRangeEnd) {
 
1057
                                        if ((endPos != startOfLine) && (s[0] == '^'))
 
1058
                                                continue;       // Can't match start of line if end position after start of line
 
1059
                                        startOfLine = endPos;
 
1060
                                }
 
1061
                                if (line == lineRangeStart) {
 
1062
                                        if ((startPos != endOfLine) && (searchEnd == '$'))
 
1063
                                                continue;       // Can't match end of line if start position before end of line
 
1064
                                        endOfLine = startPos;
 
1065
                                }
 
1066
                        }
 
1067
 
 
1068
                        DocumentIndexer di(this, endOfLine);
 
1069
                        int success = pre->Execute(di, startOfLine, endOfLine);
 
1070
                        if (success) {
 
1071
                                pos = pre->bopat[0];
 
1072
                                lenRet = pre->eopat[0] - pre->bopat[0];
 
1073
                                if (increment == -1) {
 
1074
                                        // Check for the last match on this line.
 
1075
                                        int repetitions = 1000; // Break out of infinite loop
 
1076
                                        while (success && (pre->eopat[0] <= endOfLine) && (repetitions--)) {
 
1077
                                                success = pre->Execute(di, pos+1, endOfLine);
 
1078
                                                if (success) {
 
1079
                                                        if (pre->eopat[0] <= minPos) {
 
1080
                                                                pos = pre->bopat[0];
 
1081
                                                                lenRet = pre->eopat[0] - pre->bopat[0];
 
1082
                                                        } else {
 
1083
                                                                success = 0;
 
1084
                                                        }
 
1085
                                                }
 
1086
                                        }
 
1087
                                }
 
1088
                                break;
 
1089
                        }
 
1090
                }
 
1091
                *length = lenRet;
 
1092
                return pos;
 
1093
 
 
1094
        } else {
 
1095
 
 
1096
                bool forward = minPos <= maxPos;
 
1097
                int increment = forward ? 1 : -1;
 
1098
 
 
1099
                // Range endpoints should not be inside DBCS characters, but just in case, move them.
 
1100
                int startPos = MovePositionOutsideChar(minPos, increment, false);
 
1101
                int endPos = MovePositionOutsideChar(maxPos, increment, false);
 
1102
 
 
1103
                // Compute actual search ranges needed
 
1104
                int lengthFind = *length;
 
1105
                if (lengthFind == -1)
 
1106
                        lengthFind = static_cast<int>(strlen(s));
 
1107
                int endSearch = endPos;
 
1108
                if (startPos <= endPos) {
 
1109
                        endSearch = endPos - lengthFind + 1;
 
1110
                }
 
1111
                //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
 
1112
                char firstChar = s[0];
 
1113
                if (!caseSensitive)
 
1114
                        firstChar = static_cast<char>(MakeUpperCase(firstChar));
 
1115
                int pos = forward ? startPos : (startPos - 1);
 
1116
                while (forward ? (pos < endSearch) : (pos >= endSearch)) {
 
1117
                        char ch = CharAt(pos);
 
1118
                        if (caseSensitive) {
 
1119
                                if (ch == firstChar) {
 
1120
                                        bool found = true;
 
1121
                                        if (pos + lengthFind > Platform::Maximum(startPos, endPos)) found = false;
 
1122
                                        for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
 
1123
                                                ch = CharAt(pos + posMatch);
 
1124
                                                if (ch != s[posMatch])
 
1125
                                                        found = false;
 
1126
                                        }
 
1127
                                        if (found) {
 
1128
                                                if ((!word && !wordStart) ||
 
1129
                                                        word && IsWordAt(pos, pos + lengthFind) ||
 
1130
                                                        wordStart && IsWordStartAt(pos))
 
1131
                                                        return pos;
 
1132
                                        }
 
1133
                                }
 
1134
                        } else {
 
1135
                                if (MakeUpperCase(ch) == firstChar) {
 
1136
                                        bool found = true;
 
1137
                                        if (pos + lengthFind > Platform::Maximum(startPos, endPos)) found = false;
 
1138
                                        for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) {
 
1139
                                                ch = CharAt(pos + posMatch);
 
1140
                                                if (MakeUpperCase(ch) != MakeUpperCase(s[posMatch]))
 
1141
                                                        found = false;
 
1142
                                        }
 
1143
                                        if (found) {
 
1144
                                                if ((!word && !wordStart) ||
 
1145
                                                        word && IsWordAt(pos, pos + lengthFind) ||
 
1146
                                                        wordStart && IsWordStartAt(pos))
 
1147
                                                        return pos;
 
1148
                                        }
 
1149
                                }
 
1150
                        }
 
1151
                        pos += increment;
 
1152
                        if (dbcsCodePage && (pos >= 0)) {
 
1153
                                // Ensure trying to match from start of character
 
1154
                                pos = MovePositionOutsideChar(pos, increment, false);
 
1155
                        }
 
1156
                }
 
1157
        }
 
1158
        //Platform::DebugPrintf("Not found\n");
 
1159
        return -1;
 
1160
}
 
1161
 
 
1162
const char *Document::SubstituteByPosition(const char *text, int *length) {
 
1163
        if (!pre)
 
1164
                return 0;
 
1165
        delete []substituted;
 
1166
        substituted = 0;
 
1167
        DocumentIndexer di(this, Length());
 
1168
        if (!pre->GrabMatches(di))
 
1169
                return 0;
 
1170
        unsigned int lenResult = 0;
 
1171
        for (int i = 0; i < *length; i++) {
 
1172
                if (text[i] == '\\') {
 
1173
                        if (text[i + 1] >= '1' && text[i + 1] <= '9') {
 
1174
                                unsigned int patNum = text[i + 1] - '0';
 
1175
                                lenResult += pre->eopat[patNum] - pre->bopat[patNum];
 
1176
                                i++;
 
1177
                        } else {
 
1178
                                switch (text[i + 1]) {
 
1179
                                case 'a':
 
1180
                                case 'b':
 
1181
                                case 'f':
 
1182
                                case 'n':
 
1183
                                case 'r':
 
1184
                                case 't':
 
1185
                                case 'v':
 
1186
                                        i++;
 
1187
                                }
 
1188
                                lenResult++;
 
1189
                        }
 
1190
                } else {
 
1191
                        lenResult++;
 
1192
                }
 
1193
        }
 
1194
        substituted = new char[lenResult + 1];
 
1195
        if (!substituted)
 
1196
                return 0;
 
1197
        char *o = substituted;
 
1198
        for (int j = 0; j < *length; j++) {
 
1199
                if (text[j] == '\\') {
 
1200
                        if (text[j + 1] >= '1' && text[j + 1] <= '9') {
 
1201
                                unsigned int patNum = text[j + 1] - '0';
 
1202
                                unsigned int len = pre->eopat[patNum] - pre->bopat[patNum];
 
1203
                                if (pre->pat[patNum])   // Will be null if try for a match that did not occur
 
1204
                                        memcpy(o, pre->pat[patNum], len);
 
1205
                                o += len;
 
1206
                                j++;
 
1207
                        } else {
 
1208
                                j++;
 
1209
                                switch (text[j]) {
 
1210
                                case 'a':
 
1211
                                        *o++ = '\a';
 
1212
                                        break;
 
1213
                                case 'b':
 
1214
                                        *o++ = '\b';
 
1215
                                        break;
 
1216
                                case 'f':
 
1217
                                        *o++ = '\f';
 
1218
                                        break;
 
1219
                                case 'n':
 
1220
                                        *o++ = '\n';
 
1221
                                        break;
 
1222
                                case 'r':
 
1223
                                        *o++ = '\r';
 
1224
                                        break;
 
1225
                                case 't':
 
1226
                                        *o++ = '\t';
 
1227
                                        break;
 
1228
                                case 'v':
 
1229
                                        *o++ = '\v';
 
1230
                                        break;
 
1231
                                default:
 
1232
                                        *o++ = '\\';
 
1233
                                        j--;
 
1234
                                }
 
1235
                        }
 
1236
                } else {
 
1237
                        *o++ = text[j];
 
1238
                }
 
1239
        }
 
1240
        *o = '\0';
 
1241
        *length = lenResult;
 
1242
        return substituted;
 
1243
}
 
1244
 
 
1245
int Document::LinesTotal() {
 
1246
        return cb.Lines();
 
1247
}
 
1248
 
 
1249
void Document::ChangeCase(Range r, bool makeUpperCase) {
 
1250
        for (int pos = r.start; pos < r.end;) {
 
1251
                int len = LenChar(pos);
 
1252
                if (len == 1) {
 
1253
                        char ch = CharAt(pos);
 
1254
                        if (makeUpperCase) {
 
1255
                                if (IsLowerCase(ch)) {
 
1256
                                        ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
 
1257
                                }
 
1258
                        } else {
 
1259
                                if (IsUpperCase(ch)) {
 
1260
                                        ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
 
1261
                                }
 
1262
                        }
 
1263
                }
 
1264
                pos += len;
 
1265
        }
 
1266
}
 
1267
 
 
1268
void Document::SetDefaultCharClasses(bool includeWordClass) {
 
1269
        // Initialize all char classes to default values
 
1270
        for (int ch = 0; ch < 256; ch++) {
 
1271
                if (ch == '\r' || ch == '\n')
 
1272
                        charClass[ch] = ccNewLine;
 
1273
                else if (ch < 0x20 || ch == ' ')
 
1274
                        charClass[ch] = ccSpace;
 
1275
                else if (includeWordClass && (ch >= 0x80 || isalnum(ch) || ch == '_'))
 
1276
                        charClass[ch] = ccWord;
 
1277
                else
 
1278
                        charClass[ch] = ccPunctuation;
 
1279
        }
 
1280
}
 
1281
 
 
1282
void Document::SetCharClasses(const unsigned char *chars, charClassification newCharClass) {
 
1283
        // Apply the newCharClass to the specifed chars
 
1284
        if (chars) {
 
1285
                while (*chars) {
 
1286
                        charClass[*chars] = newCharClass;
 
1287
                        chars++;
 
1288
                }
 
1289
        }
 
1290
}
 
1291
 
 
1292
void Document::SetStylingBits(int bits) {
 
1293
        stylingBits = bits;
 
1294
        stylingBitsMask = 0;
 
1295
        for (int bit = 0; bit < stylingBits; bit++) {
 
1296
                stylingBitsMask <<= 1;
 
1297
                stylingBitsMask |= 1;
 
1298
        }
 
1299
}
 
1300
 
 
1301
void Document::StartStyling(int position, char mask) {
 
1302
        stylingMask = mask;
 
1303
        endStyled = position;
 
1304
}
 
1305
 
 
1306
bool Document::SetStyleFor(int length, char style) {
 
1307
        if (enteredCount != 0) {
 
1308
                return false;
 
1309
        } else {
 
1310
                enteredCount++;
 
1311
                style &= stylingMask;
 
1312
                int prevEndStyled = endStyled;
 
1313
                if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {
 
1314
                        DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
 
1315
                                           prevEndStyled, length);
 
1316
                        NotifyModified(mh);
 
1317
                }
 
1318
                endStyled += length;
 
1319
                enteredCount--;
 
1320
                return true;
 
1321
        }
 
1322
}
 
1323
 
 
1324
bool Document::SetStyles(int length, char *styles) {
 
1325
        if (enteredCount != 0) {
 
1326
                return false;
 
1327
        } else {
 
1328
                enteredCount++;
 
1329
                bool didChange = false;
 
1330
                int startMod = 0;
 
1331
                int endMod = 0;
 
1332
                for (int iPos = 0; iPos < length; iPos++, endStyled++) {
 
1333
                        PLATFORM_ASSERT(endStyled < Length());
 
1334
                        if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
 
1335
                                if (!didChange) {
 
1336
                                        startMod = endStyled;
 
1337
                                }
 
1338
                                didChange = true;
 
1339
                                endMod = endStyled;
 
1340
                        }
 
1341
                }
 
1342
                if (didChange) {
 
1343
                        DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
 
1344
                                           startMod, endMod - startMod + 1);
 
1345
                        NotifyModified(mh);
 
1346
                }
 
1347
                enteredCount--;
 
1348
                return true;
 
1349
        }
 
1350
}
 
1351
 
 
1352
bool Document::EnsureStyledTo(int pos) {
 
1353
        if (pos > GetEndStyled()) {
 
1354
                IncrementStyleClock();
 
1355
                // Ask the watchers to style, and stop as soon as one responds.
 
1356
                for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++) {
 
1357
                        watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos);
 
1358
                }
 
1359
        }
 
1360
        return pos <= GetEndStyled();
 
1361
}
 
1362
 
 
1363
void Document::IncrementStyleClock() {
 
1364
        styleClock++;
 
1365
        if (styleClock > 0x100000) {
 
1366
                styleClock = 0;
 
1367
        }
 
1368
}
 
1369
 
 
1370
bool Document::AddWatcher(DocWatcher *watcher, void *userData) {
 
1371
        for (int i = 0; i < lenWatchers; i++) {
 
1372
                if ((watchers[i].watcher == watcher) &&
 
1373
                        (watchers[i].userData == userData))
 
1374
                        return false;
 
1375
        }
 
1376
        WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1];
 
1377
        if (!pwNew)
 
1378
                return false;
 
1379
        for (int j = 0; j < lenWatchers; j++)
 
1380
                pwNew[j] = watchers[j];
 
1381
        pwNew[lenWatchers].watcher = watcher;
 
1382
        pwNew[lenWatchers].userData = userData;
 
1383
        delete []watchers;
 
1384
        watchers = pwNew;
 
1385
        lenWatchers++;
 
1386
        return true;
 
1387
}
 
1388
 
 
1389
bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) {
 
1390
        for (int i = 0; i < lenWatchers; i++) {
 
1391
                if ((watchers[i].watcher == watcher) &&
 
1392
                        (watchers[i].userData == userData)) {
 
1393
                        if (lenWatchers == 1) {
 
1394
                                delete []watchers;
 
1395
                                watchers = 0;
 
1396
                                lenWatchers = 0;
 
1397
                        } else {
 
1398
                                WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers];
 
1399
                                if (!pwNew)
 
1400
                                        return false;
 
1401
                                for (int j = 0; j < lenWatchers - 1; j++) {
 
1402
                                        pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1];
 
1403
                                }
 
1404
                                delete []watchers;
 
1405
                                watchers = pwNew;
 
1406
                                lenWatchers--;
 
1407
                        }
 
1408
                        return true;
 
1409
                }
 
1410
        }
 
1411
        return false;
 
1412
}
 
1413
 
 
1414
void Document::NotifyModifyAttempt() {
 
1415
        for (int i = 0; i < lenWatchers; i++) {
 
1416
                watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
 
1417
        }
 
1418
}
 
1419
 
 
1420
void Document::NotifySavePoint(bool atSavePoint) {
 
1421
        for (int i = 0; i < lenWatchers; i++) {
 
1422
                watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint);
 
1423
        }
 
1424
}
 
1425
 
 
1426
void Document::NotifyModified(DocModification mh) {
 
1427
        for (int i = 0; i < lenWatchers; i++) {
 
1428
                watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData);
 
1429
        }
 
1430
}
 
1431
 
 
1432
bool Document::IsWordPartSeparator(char ch) {
 
1433
        return (WordCharClass(ch) == ccWord) && IsPunctuation(ch);
 
1434
}
 
1435
 
 
1436
int Document::WordPartLeft(int pos) {
 
1437
        if (pos > 0) {
 
1438
                --pos;
 
1439
                char startChar = cb.CharAt(pos);
 
1440
                if (IsWordPartSeparator(startChar)) {
 
1441
                        while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) {
 
1442
                                --pos;
 
1443
                        }
 
1444
                }
 
1445
                if (pos > 0) {
 
1446
                        startChar = cb.CharAt(pos);
 
1447
                        --pos;
 
1448
                        if (IsLowerCase(startChar)) {
 
1449
                                while (pos > 0 && IsLowerCase(cb.CharAt(pos)))
 
1450
                                        --pos;
 
1451
                                if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos)))
 
1452
                                        ++pos;
 
1453
                        } else if (IsUpperCase(startChar)) {
 
1454
                                while (pos > 0 && IsUpperCase(cb.CharAt(pos)))
 
1455
                                        --pos;
 
1456
                                if (!IsUpperCase(cb.CharAt(pos)))
 
1457
                                        ++pos;
 
1458
                        } else if (IsADigit(startChar)) {
 
1459
                                while (pos > 0 && IsADigit(cb.CharAt(pos)))
 
1460
                                        --pos;
 
1461
                                if (!IsADigit(cb.CharAt(pos)))
 
1462
                                        ++pos;
 
1463
                        } else if (IsPunctuation(startChar)) {
 
1464
                                while (pos > 0 && IsPunctuation(cb.CharAt(pos)))
 
1465
                                        --pos;
 
1466
                                if (!IsPunctuation(cb.CharAt(pos)))
 
1467
                                        ++pos;
 
1468
                        } else if (isspacechar(startChar)) {
 
1469
                                while (pos > 0 && isspacechar(cb.CharAt(pos)))
 
1470
                                        --pos;
 
1471
                                if (!isspacechar(cb.CharAt(pos)))
 
1472
                                        ++pos;
 
1473
                        } else if (!isascii(startChar)) {
 
1474
                                while (pos > 0 && !isascii(cb.CharAt(pos)))
 
1475
                                        --pos;
 
1476
                                if (isascii(cb.CharAt(pos)))
 
1477
                                        ++pos;
 
1478
                        } else {
 
1479
                                ++pos;
 
1480
                        }
 
1481
                }
 
1482
        }
 
1483
        return pos;
 
1484
}
 
1485
 
 
1486
int Document::WordPartRight(int pos) {
 
1487
        char startChar = cb.CharAt(pos);
 
1488
        int length = Length();
 
1489
        if (IsWordPartSeparator(startChar)) {
 
1490
                while (pos < length && IsWordPartSeparator(cb.CharAt(pos)))
 
1491
                        ++pos;
 
1492
                startChar = cb.CharAt(pos);
 
1493
        }
 
1494
        if (!isascii(startChar)) {
 
1495
                while (pos < length && !isascii(cb.CharAt(pos)))
 
1496
                        ++pos;
 
1497
        } else if (IsLowerCase(startChar)) {
 
1498
                while (pos < length && IsLowerCase(cb.CharAt(pos)))
 
1499
                        ++pos;
 
1500
        } else if (IsUpperCase(startChar)) {
 
1501
                if (IsLowerCase(cb.CharAt(pos + 1))) {
 
1502
                        ++pos;
 
1503
                        while (pos < length && IsLowerCase(cb.CharAt(pos)))
 
1504
                                ++pos;
 
1505
                } else {
 
1506
                        while (pos < length && IsUpperCase(cb.CharAt(pos)))
 
1507
                                ++pos;
 
1508
                }
 
1509
                if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1)))
 
1510
                        --pos;
 
1511
        } else if (IsADigit(startChar)) {
 
1512
                while (pos < length && IsADigit(cb.CharAt(pos)))
 
1513
                        ++pos;
 
1514
        } else if (IsPunctuation(startChar)) {
 
1515
                while (pos < length && IsPunctuation(cb.CharAt(pos)))
 
1516
                        ++pos;
 
1517
        } else if (isspacechar(startChar)) {
 
1518
                while (pos < length && isspacechar(cb.CharAt(pos)))
 
1519
                        ++pos;
 
1520
        } else {
 
1521
                ++pos;
 
1522
        }
 
1523
        return pos;
 
1524
}
 
1525
 
 
1526
bool IsLineEndChar(char c) {
 
1527
        return (c == '\n' || c == '\r');
 
1528
}
 
1529
 
 
1530
int Document::ExtendStyleRange(int pos, int delta, bool singleLine) {
 
1531
        int sStart = cb.StyleAt(pos);
 
1532
        if (delta < 0) {
 
1533
                while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )
 
1534
                        pos--;
 
1535
                pos++;
 
1536
        } else {
 
1537
                while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )
 
1538
                        pos++;
 
1539
        }
 
1540
        return pos;
 
1541
}
 
1542
 
 
1543
static char BraceOpposite(char ch) {
 
1544
        switch (ch) {
 
1545
        case '(':
 
1546
                return ')';
 
1547
        case ')':
 
1548
                return '(';
 
1549
        case '[':
 
1550
                return ']';
 
1551
        case ']':
 
1552
                return '[';
 
1553
        case '{':
 
1554
                return '}';
 
1555
        case '}':
 
1556
                return '{';
 
1557
        case '<':
 
1558
                return '>';
 
1559
        case '>':
 
1560
                return '<';
 
1561
        default:
 
1562
                return '\0';
 
1563
        }
 
1564
}
 
1565
 
 
1566
// TODO: should be able to extend styled region to find matching brace
 
1567
int Document::BraceMatch(int position, int /*maxReStyle*/) {
 
1568
        char chBrace = CharAt(position);
 
1569
        char chSeek = BraceOpposite(chBrace);
 
1570
        if (chSeek == '\0')
 
1571
                return - 1;
 
1572
        char styBrace = static_cast<char>(StyleAt(position) & stylingBitsMask);
 
1573
        int direction = -1;
 
1574
        if (chBrace == '(' || chBrace == '[' || chBrace == '{' || chBrace == '<')
 
1575
                direction = 1;
 
1576
        int depth = 1;
 
1577
        position = position + direction;
 
1578
        while ((position >= 0) && (position < Length())) {
 
1579
                position = MovePositionOutsideChar(position, direction);
 
1580
                char chAtPos = CharAt(position);
 
1581
                char styAtPos = static_cast<char>(StyleAt(position) & stylingBitsMask);
 
1582
                if ((position > GetEndStyled()) || (styAtPos == styBrace)) {
 
1583
                        if (chAtPos == chBrace)
 
1584
                                depth++;
 
1585
                        if (chAtPos == chSeek)
 
1586
                                depth--;
 
1587
                        if (depth == 0)
 
1588
                                return position;
 
1589
                }
 
1590
                position = position + direction;
 
1591
        }
 
1592
        return - 1;
 
1593
}