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