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