]>
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; |
8e54aaed | 53 | SetDefaultCharClasses(); |
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 | } | |
8e54aaed | 764 | return MovePositionOutsideChar(pos, delta); |
9ce192d4 RD |
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 | ||
8e54aaed RD |
794 | /** |
795 | * Find the end of the next word in either a forward (delta >= 0) or backwards direction | |
796 | * (delta < 0). | |
797 | * This is looking for a transition between character classes although there is also some | |
798 | * additional movement to transit white space. | |
799 | * Used by cursor movement by word commands. | |
800 | */ | |
801 | int Document::NextWordEnd(int pos, int delta) { | |
802 | if (delta < 0) { | |
803 | if (pos > 0) { | |
804 | charClassification ccStart = WordCharClass(cb.CharAt(pos-1)); | |
805 | if (ccStart != ccSpace) { | |
806 | while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) { | |
807 | pos--; | |
808 | } | |
809 | } | |
810 | while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccSpace) { | |
811 | pos--; | |
812 | } | |
813 | } | |
814 | } else { | |
815 | while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccSpace) { | |
816 | pos++; | |
817 | } | |
818 | if (pos < Length()) { | |
819 | charClassification ccStart = WordCharClass(cb.CharAt(pos)); | |
820 | while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) { | |
821 | pos++; | |
822 | } | |
823 | } | |
824 | } | |
825 | return pos; | |
826 | } | |
827 | ||
65ec6247 | 828 | /** |
1a2fb4cd RD |
829 | * Check that the character at the given position is a word or punctuation character and that |
830 | * the previous character is of a different character class. | |
65ec6247 | 831 | */ |
d134f170 RD |
832 | bool Document::IsWordStartAt(int pos) { |
833 | if (pos > 0) { | |
1a2fb4cd RD |
834 | charClassification ccPos = WordCharClass(CharAt(pos)); |
835 | return (ccPos == ccWord || ccPos == ccPunctuation) && | |
836 | (ccPos != WordCharClass(CharAt(pos - 1))); | |
9ce192d4 | 837 | } |
d134f170 RD |
838 | return true; |
839 | } | |
840 | ||
65ec6247 | 841 | /** |
1a2fb4cd RD |
842 | * Check that the character at the given position is a word or punctuation character and that |
843 | * the next character is of a different character class. | |
65ec6247 | 844 | */ |
d134f170 RD |
845 | bool Document::IsWordEndAt(int pos) { |
846 | if (pos < Length() - 1) { | |
1a2fb4cd RD |
847 | charClassification ccPrev = WordCharClass(CharAt(pos-1)); |
848 | return (ccPrev == ccWord || ccPrev == ccPunctuation) && | |
849 | (ccPrev != WordCharClass(CharAt(pos))); | |
9ce192d4 RD |
850 | } |
851 | return true; | |
852 | } | |
853 | ||
65ec6247 | 854 | /** |
9e730a78 | 855 | * Check that the given range is has transitions between character classes at both |
1a2fb4cd | 856 | * ends and where the characters on the inside are word or punctuation characters. |
65ec6247 | 857 | */ |
d134f170 RD |
858 | bool Document::IsWordAt(int start, int end) { |
859 | return IsWordStartAt(start) && IsWordEndAt(end); | |
860 | } | |
861 | ||
65ec6247 RD |
862 | // The comparison and case changing functions here assume ASCII |
863 | // or extended ASCII such as the normal Windows code page. | |
864 | ||
865 | static inline char MakeUpperCase(char ch) { | |
866 | if (ch < 'a' || ch > 'z') | |
867 | return ch; | |
868 | else | |
869 | return static_cast<char>(ch - 'a' + 'A'); | |
870 | } | |
871 | ||
872 | static inline char MakeLowerCase(char ch) { | |
873 | if (ch < 'A' || ch > 'Z') | |
874 | return ch; | |
875 | else | |
876 | return static_cast<char>(ch - 'A' + 'a'); | |
877 | } | |
878 | ||
879 | // Define a way for the Regular Expression code to access the document | |
880 | class DocumentIndexer : public CharacterIndexer { | |
881 | Document *pdoc; | |
882 | int end; | |
883 | public: | |
a834585d RD |
884 | DocumentIndexer(Document *pdoc_, int end_) : |
885 | pdoc(pdoc_), end(end_) { | |
886 | } | |
65ec6247 RD |
887 | |
888 | virtual char CharAt(int index) { | |
889 | if (index < 0 || index >= end) | |
890 | return 0; | |
891 | else | |
892 | return pdoc->CharAt(index); | |
893 | } | |
894 | }; | |
895 | ||
896 | /** | |
897 | * Find text in document, supporting both forward and backward | |
898 | * searches (just pass minPos > maxPos to do a backward search) | |
899 | * Has not been tested with backwards DBCS searches yet. | |
900 | */ | |
901 | long Document::FindText(int minPos, int maxPos, const char *s, | |
9e730a78 | 902 | bool caseSensitive, bool word, bool wordStart, bool regExp, bool posix, |
65ec6247 RD |
903 | int *length) { |
904 | if (regExp) { | |
905 | if (!pre) | |
906 | pre = new RESearch(); | |
907 | if (!pre) | |
908 | return -1; | |
909 | ||
9e730a78 | 910 | int increment = (minPos <= maxPos) ? 1 : -1; |
65ec6247 | 911 | |
9e730a78 RD |
912 | int startPos = minPos; |
913 | int endPos = maxPos; | |
65ec6247 RD |
914 | |
915 | // Range endpoints should not be inside DBCS characters, but just in case, move them. | |
916 | startPos = MovePositionOutsideChar(startPos, 1, false); | |
917 | endPos = MovePositionOutsideChar(endPos, 1, false); | |
918 | ||
9e730a78 | 919 | const char *errmsg = pre->Compile(s, *length, caseSensitive, posix); |
65ec6247 RD |
920 | if (errmsg) { |
921 | return -1; | |
922 | } | |
923 | // Find a variable in a property file: \$(\([A-Za-z0-9_.]+\)) | |
924 | // Replace first '.' with '-' in each property file variable reference: | |
925 | // Search: \$(\([A-Za-z0-9_-]+\)\.\([A-Za-z0-9_.]+\)) | |
926 | // Replace: $(\1-\2) | |
927 | int lineRangeStart = LineFromPosition(startPos); | |
928 | int lineRangeEnd = LineFromPosition(endPos); | |
9e730a78 RD |
929 | if ((increment == 1) && |
930 | (startPos >= LineEnd(lineRangeStart)) && | |
931 | (lineRangeStart < lineRangeEnd)) { | |
65ec6247 RD |
932 | // the start position is at end of line or between line end characters. |
933 | lineRangeStart++; | |
934 | startPos = LineStart(lineRangeStart); | |
935 | } | |
936 | int pos = -1; | |
937 | int lenRet = 0; | |
938 | char searchEnd = s[*length - 1]; | |
9e730a78 RD |
939 | int lineRangeBreak = lineRangeEnd + increment; |
940 | for (int line = lineRangeStart; line != lineRangeBreak; line += increment) { | |
65ec6247 RD |
941 | int startOfLine = LineStart(line); |
942 | int endOfLine = LineEnd(line); | |
9e730a78 RD |
943 | if (increment == 1) { |
944 | if (line == lineRangeStart) { | |
945 | if ((startPos != startOfLine) && (s[0] == '^')) | |
946 | continue; // Can't match start of line if start position after start of line | |
947 | startOfLine = startPos; | |
948 | } | |
949 | if (line == lineRangeEnd) { | |
950 | if ((endPos != endOfLine) && (searchEnd == '$')) | |
951 | continue; // Can't match end of line if end position before end of line | |
952 | endOfLine = endPos; | |
953 | } | |
954 | } else { | |
955 | if (line == lineRangeEnd) { | |
956 | if ((endPos != startOfLine) && (s[0] == '^')) | |
957 | continue; // Can't match start of line if end position after start of line | |
958 | startOfLine = endPos; | |
959 | } | |
960 | if (line == lineRangeStart) { | |
961 | if ((startPos != endOfLine) && (searchEnd == '$')) | |
962 | continue; // Can't match end of line if start position before end of line | |
88a8b04e | 963 | endOfLine = startPos+1; |
9e730a78 | 964 | } |
65ec6247 | 965 | } |
9e730a78 | 966 | |
65ec6247 RD |
967 | DocumentIndexer di(this, endOfLine); |
968 | int success = pre->Execute(di, startOfLine, endOfLine); | |
969 | if (success) { | |
970 | pos = pre->bopat[0]; | |
971 | lenRet = pre->eopat[0] - pre->bopat[0]; | |
9e730a78 RD |
972 | if (increment == -1) { |
973 | // Check for the last match on this line. | |
974 | int repetitions = 1000; // Break out of infinite loop | |
88a8b04e RD |
975 | while (success && (pre->eopat[0] <= (endOfLine+1)) && (repetitions--)) { |
976 | success = pre->Execute(di, pos+1, endOfLine+1); | |
9e730a78 | 977 | if (success) { |
88a8b04e | 978 | if (pre->eopat[0] <= (minPos+1)) { |
9e730a78 RD |
979 | pos = pre->bopat[0]; |
980 | lenRet = pre->eopat[0] - pre->bopat[0]; | |
981 | } else { | |
982 | success = 0; | |
983 | } | |
984 | } | |
985 | } | |
986 | } | |
65ec6247 RD |
987 | break; |
988 | } | |
989 | } | |
990 | *length = lenRet; | |
991 | return pos; | |
992 | ||
993 | } else { | |
994 | ||
995 | bool forward = minPos <= maxPos; | |
996 | int increment = forward ? 1 : -1; | |
997 | ||
998 | // Range endpoints should not be inside DBCS characters, but just in case, move them. | |
999 | int startPos = MovePositionOutsideChar(minPos, increment, false); | |
1000 | int endPos = MovePositionOutsideChar(maxPos, increment, false); | |
1001 | ||
1002 | // Compute actual search ranges needed | |
1003 | int lengthFind = *length; | |
1004 | if (lengthFind == -1) | |
a834585d | 1005 | lengthFind = static_cast<int>(strlen(s)); |
65ec6247 RD |
1006 | int endSearch = endPos; |
1007 | if (startPos <= endPos) { | |
1008 | endSearch = endPos - lengthFind + 1; | |
1009 | } | |
1010 | //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind); | |
1011 | char firstChar = s[0]; | |
1012 | if (!caseSensitive) | |
1013 | firstChar = static_cast<char>(MakeUpperCase(firstChar)); | |
1014 | int pos = startPos; | |
1015 | while (forward ? (pos < endSearch) : (pos >= endSearch)) { | |
1016 | char ch = CharAt(pos); | |
1017 | if (caseSensitive) { | |
1018 | if (ch == firstChar) { | |
1019 | bool found = true; | |
1020 | for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) { | |
1021 | ch = CharAt(pos + posMatch); | |
1022 | if (ch != s[posMatch]) | |
1023 | found = false; | |
1024 | } | |
1025 | if (found) { | |
1026 | if ((!word && !wordStart) || | |
1027 | word && IsWordAt(pos, pos + lengthFind) || | |
1028 | wordStart && IsWordStartAt(pos)) | |
1029 | return pos; | |
1030 | } | |
9ce192d4 | 1031 | } |
65ec6247 RD |
1032 | } else { |
1033 | if (MakeUpperCase(ch) == firstChar) { | |
1034 | bool found = true; | |
1035 | for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) { | |
1036 | ch = CharAt(pos + posMatch); | |
1037 | if (MakeUpperCase(ch) != MakeUpperCase(s[posMatch])) | |
1038 | found = false; | |
1039 | } | |
1040 | if (found) { | |
1041 | if ((!word && !wordStart) || | |
1042 | word && IsWordAt(pos, pos + lengthFind) || | |
1043 | wordStart && IsWordStartAt(pos)) | |
1044 | return pos; | |
1045 | } | |
9ce192d4 RD |
1046 | } |
1047 | } | |
65ec6247 | 1048 | pos += increment; |
8e54aaed | 1049 | if (dbcsCodePage && (pos >= 0)) { |
65ec6247 RD |
1050 | // Ensure trying to match from start of character |
1051 | pos = MovePositionOutsideChar(pos, increment, false); | |
1052 | } | |
9ce192d4 RD |
1053 | } |
1054 | } | |
1055 | //Platform::DebugPrintf("Not found\n"); | |
65ec6247 RD |
1056 | return -1; |
1057 | } | |
1058 | ||
1059 | const char *Document::SubstituteByPosition(const char *text, int *length) { | |
1060 | if (!pre) | |
1061 | return 0; | |
1062 | delete []substituted; | |
1063 | substituted = 0; | |
1064 | DocumentIndexer di(this, Length()); | |
1065 | if (!pre->GrabMatches(di)) | |
1066 | return 0; | |
1067 | unsigned int lenResult = 0; | |
1068 | for (int i = 0; i < *length; i++) { | |
8e54aaed RD |
1069 | if (text[i] == '\\') { |
1070 | if (text[i + 1] >= '1' && text[i + 1] <= '9') { | |
1071 | unsigned int patNum = text[i + 1] - '0'; | |
1072 | lenResult += pre->eopat[patNum] - pre->bopat[patNum]; | |
1073 | i++; | |
1074 | } else { | |
1075 | switch (text[i + 1]) { | |
1076 | case 'a': | |
1077 | case 'b': | |
1078 | case 'f': | |
1079 | case 'n': | |
1080 | case 'r': | |
1081 | case 't': | |
1082 | case 'v': | |
1083 | i++; | |
1084 | } | |
1085 | lenResult++; | |
1086 | } | |
65ec6247 RD |
1087 | } else { |
1088 | lenResult++; | |
1089 | } | |
1090 | } | |
1091 | substituted = new char[lenResult + 1]; | |
1092 | if (!substituted) | |
1093 | return 0; | |
1094 | char *o = substituted; | |
1095 | for (int j = 0; j < *length; j++) { | |
8e54aaed RD |
1096 | if (text[j] == '\\') { |
1097 | if (text[j + 1] >= '1' && text[j + 1] <= '9') { | |
1098 | unsigned int patNum = text[j + 1] - '0'; | |
1099 | unsigned int len = pre->eopat[patNum] - pre->bopat[patNum]; | |
1100 | if (pre->pat[patNum]) // Will be null if try for a match that did not occur | |
1101 | memcpy(o, pre->pat[patNum], len); | |
1102 | o += len; | |
1103 | j++; | |
1104 | } else { | |
1105 | j++; | |
1106 | switch (text[j]) { | |
1107 | case 'a': | |
1108 | *o++ = '\a'; | |
1109 | break; | |
1110 | case 'b': | |
1111 | *o++ = '\b'; | |
1112 | break; | |
1113 | case 'f': | |
1114 | *o++ = '\f'; | |
1115 | break; | |
1116 | case 'n': | |
1117 | *o++ = '\n'; | |
1118 | break; | |
1119 | case 'r': | |
1120 | *o++ = '\r'; | |
1121 | break; | |
1122 | case 't': | |
1123 | *o++ = '\t'; | |
1124 | break; | |
1125 | case 'v': | |
1126 | *o++ = '\v'; | |
1127 | break; | |
1128 | default: | |
1129 | *o++ = '\\'; | |
1130 | j--; | |
1131 | } | |
1132 | } | |
65ec6247 RD |
1133 | } else { |
1134 | *o++ = text[j]; | |
1135 | } | |
1136 | } | |
1137 | *o = '\0'; | |
1138 | *length = lenResult; | |
1139 | return substituted; | |
9ce192d4 RD |
1140 | } |
1141 | ||
1142 | int Document::LinesTotal() { | |
1143 | return cb.Lines(); | |
1144 | } | |
1145 | ||
f6bcfd97 | 1146 | void Document::ChangeCase(Range r, bool makeUpperCase) { |
65ec6247 | 1147 | for (int pos = r.start; pos < r.end; pos++) { |
9e730a78 RD |
1148 | int len = LenChar(pos); |
1149 | if (dbcsCodePage && (len > 1)) { | |
1150 | pos += len; | |
f6bcfd97 | 1151 | } else { |
9e730a78 | 1152 | char ch = CharAt(pos); |
f6bcfd97 | 1153 | if (makeUpperCase) { |
9e730a78 | 1154 | if (IsLowerCase(ch)) { |
65ec6247 | 1155 | ChangeChar(pos, static_cast<char>(MakeUpperCase(ch))); |
f6bcfd97 BP |
1156 | } |
1157 | } else { | |
9e730a78 | 1158 | if (IsUpperCase(ch)) { |
65ec6247 | 1159 | ChangeChar(pos, static_cast<char>(MakeLowerCase(ch))); |
f6bcfd97 BP |
1160 | } |
1161 | } | |
1162 | } | |
1163 | } | |
1164 | } | |
1165 | ||
8e54aaed RD |
1166 | void Document::SetDefaultCharClasses() { |
1167 | // Initialize all char classes to default values | |
1168 | for (int ch = 0; ch < 256; ch++) { | |
1a2fb4cd RD |
1169 | if (ch == '\r' || ch == '\n') |
1170 | charClass[ch] = ccNewLine; | |
1171 | else if (ch < 0x20 || ch == ' ') | |
1172 | charClass[ch] = ccSpace; | |
8e54aaed RD |
1173 | else if (ch >= 0x80 || isalnum(ch) || ch == '_') |
1174 | charClass[ch] = ccWord; | |
1a2fb4cd RD |
1175 | else |
1176 | charClass[ch] = ccPunctuation; | |
9ce192d4 | 1177 | } |
8e54aaed RD |
1178 | } |
1179 | ||
1180 | void Document::SetCharClasses(unsigned char *chars, charClassification newCharClass) { | |
1181 | // Apply the newCharClass to the specifed chars | |
9ce192d4 RD |
1182 | if (chars) { |
1183 | while (*chars) { | |
8e54aaed | 1184 | charClass[*chars] = newCharClass; |
9ce192d4 RD |
1185 | chars++; |
1186 | } | |
9ce192d4 RD |
1187 | } |
1188 | } | |
1189 | ||
1190 | void Document::SetStylingBits(int bits) { | |
1191 | stylingBits = bits; | |
1192 | stylingBitsMask = 0; | |
65ec6247 | 1193 | for (int bit = 0; bit < stylingBits; bit++) { |
9ce192d4 RD |
1194 | stylingBitsMask <<= 1; |
1195 | stylingBitsMask |= 1; | |
1196 | } | |
1197 | } | |
1198 | ||
1199 | void Document::StartStyling(int position, char mask) { | |
9ce192d4 | 1200 | stylingMask = mask; |
65ec6247 | 1201 | endStyled = position; |
9ce192d4 RD |
1202 | } |
1203 | ||
a834585d RD |
1204 | bool Document::SetStyleFor(int length, char style) { |
1205 | if (enteredCount != 0) { | |
1206 | return false; | |
1207 | } else { | |
9ce192d4 | 1208 | enteredCount++; |
9e730a78 | 1209 | style &= stylingMask; |
9ce192d4 | 1210 | int prevEndStyled = endStyled; |
65ec6247 RD |
1211 | if (cb.SetStyleFor(endStyled, length, style, stylingMask)) { |
1212 | DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER, | |
1213 | prevEndStyled, length); | |
9ce192d4 RD |
1214 | NotifyModified(mh); |
1215 | } | |
65ec6247 | 1216 | endStyled += length; |
9ce192d4 | 1217 | enteredCount--; |
a834585d | 1218 | return true; |
9ce192d4 RD |
1219 | } |
1220 | } | |
1221 | ||
a834585d RD |
1222 | bool Document::SetStyles(int length, char *styles) { |
1223 | if (enteredCount != 0) { | |
1224 | return false; | |
1225 | } else { | |
9ce192d4 RD |
1226 | enteredCount++; |
1227 | int prevEndStyled = endStyled; | |
1228 | bool didChange = false; | |
a834585d | 1229 | int lastChange = 0; |
65ec6247 | 1230 | for (int iPos = 0; iPos < length; iPos++, endStyled++) { |
1a2fb4cd | 1231 | PLATFORM_ASSERT(endStyled < Length()); |
65ec6247 | 1232 | if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) { |
9ce192d4 | 1233 | didChange = true; |
a834585d | 1234 | lastChange = iPos; |
9ce192d4 RD |
1235 | } |
1236 | } | |
9ce192d4 | 1237 | if (didChange) { |
65ec6247 | 1238 | DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER, |
a834585d | 1239 | prevEndStyled, lastChange); |
9ce192d4 RD |
1240 | NotifyModified(mh); |
1241 | } | |
1242 | enteredCount--; | |
a834585d | 1243 | return true; |
9ce192d4 RD |
1244 | } |
1245 | } | |
1246 | ||
f6bcfd97 | 1247 | bool Document::EnsureStyledTo(int pos) { |
1a2fb4cd RD |
1248 | if (pos > GetEndStyled()) { |
1249 | styleClock++; | |
1250 | if (styleClock > 0x100000) { | |
1251 | styleClock = 0; | |
1252 | } | |
a834585d RD |
1253 | // Ask the watchers to style, and stop as soon as one responds. |
1254 | for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++) { | |
1255 | watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos); | |
1256 | } | |
1a2fb4cd | 1257 | } |
f6bcfd97 BP |
1258 | return pos <= GetEndStyled(); |
1259 | } | |
1260 | ||
9ce192d4 RD |
1261 | bool Document::AddWatcher(DocWatcher *watcher, void *userData) { |
1262 | for (int i = 0; i < lenWatchers; i++) { | |
1263 | if ((watchers[i].watcher == watcher) && | |
1264 | (watchers[i].userData == userData)) | |
1265 | return false; | |
1266 | } | |
1267 | WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1]; | |
1268 | if (!pwNew) | |
1269 | return false; | |
1270 | for (int j = 0; j < lenWatchers; j++) | |
1271 | pwNew[j] = watchers[j]; | |
1272 | pwNew[lenWatchers].watcher = watcher; | |
1273 | pwNew[lenWatchers].userData = userData; | |
1274 | delete []watchers; | |
1275 | watchers = pwNew; | |
1276 | lenWatchers++; | |
1277 | return true; | |
1278 | } | |
1279 | ||
1280 | bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) { | |
1281 | for (int i = 0; i < lenWatchers; i++) { | |
1282 | if ((watchers[i].watcher == watcher) && | |
1283 | (watchers[i].userData == userData)) { | |
1284 | if (lenWatchers == 1) { | |
1285 | delete []watchers; | |
1286 | watchers = 0; | |
1287 | lenWatchers = 0; | |
1288 | } else { | |
1289 | WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers]; | |
1290 | if (!pwNew) | |
1291 | return false; | |
1292 | for (int j = 0; j < lenWatchers - 1; j++) { | |
1293 | pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1]; | |
1294 | } | |
1295 | delete []watchers; | |
1296 | watchers = pwNew; | |
1297 | lenWatchers--; | |
1298 | } | |
1299 | return true; | |
1300 | } | |
1301 | } | |
1302 | return false; | |
1303 | } | |
1304 | ||
1305 | void Document::NotifyModifyAttempt() { | |
1306 | for (int i = 0; i < lenWatchers; i++) { | |
1307 | watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData); | |
1308 | } | |
1309 | } | |
1310 | ||
1311 | void Document::NotifySavePoint(bool atSavePoint) { | |
1312 | for (int i = 0; i < lenWatchers; i++) { | |
1313 | watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint); | |
1314 | } | |
1315 | } | |
1316 | ||
1317 | void Document::NotifyModified(DocModification mh) { | |
1318 | for (int i = 0; i < lenWatchers; i++) { | |
1319 | watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData); | |
1320 | } | |
1321 | } | |
65ec6247 RD |
1322 | |
1323 | bool Document::IsWordPartSeparator(char ch) { | |
9e730a78 | 1324 | return (WordCharClass(ch) == ccWord) && IsPunctuation(ch); |
65ec6247 RD |
1325 | } |
1326 | ||
1327 | int Document::WordPartLeft(int pos) { | |
1328 | if (pos > 0) { | |
1329 | --pos; | |
1330 | char startChar = cb.CharAt(pos); | |
1331 | if (IsWordPartSeparator(startChar)) { | |
1332 | while (pos > 0 && IsWordPartSeparator(cb.CharAt(pos))) { | |
1333 | --pos; | |
1334 | } | |
1335 | } | |
1336 | if (pos > 0) { | |
1337 | startChar = cb.CharAt(pos); | |
1338 | --pos; | |
9e730a78 RD |
1339 | if (IsLowerCase(startChar)) { |
1340 | while (pos > 0 && IsLowerCase(cb.CharAt(pos))) | |
65ec6247 | 1341 | --pos; |
9e730a78 | 1342 | if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos))) |
65ec6247 | 1343 | ++pos; |
9e730a78 RD |
1344 | } else if (IsUpperCase(startChar)) { |
1345 | while (pos > 0 && IsUpperCase(cb.CharAt(pos))) | |
65ec6247 | 1346 | --pos; |
9e730a78 | 1347 | if (!IsUpperCase(cb.CharAt(pos))) |
65ec6247 | 1348 | ++pos; |
9e730a78 RD |
1349 | } else if (IsADigit(startChar)) { |
1350 | while (pos > 0 && IsADigit(cb.CharAt(pos))) | |
65ec6247 | 1351 | --pos; |
9e730a78 | 1352 | if (!IsADigit(cb.CharAt(pos))) |
65ec6247 | 1353 | ++pos; |
9e730a78 RD |
1354 | } else if (IsPunctuation(startChar)) { |
1355 | while (pos > 0 && IsPunctuation(cb.CharAt(pos))) | |
65ec6247 | 1356 | --pos; |
9e730a78 | 1357 | if (!IsPunctuation(cb.CharAt(pos))) |
65ec6247 RD |
1358 | ++pos; |
1359 | } else if (isspacechar(startChar)) { | |
1360 | while (pos > 0 && isspacechar(cb.CharAt(pos))) | |
1361 | --pos; | |
1362 | if (!isspacechar(cb.CharAt(pos))) | |
1363 | ++pos; | |
9e730a78 RD |
1364 | } else if (!isascii(startChar)) { |
1365 | while (pos > 0 && !isascii(cb.CharAt(pos))) | |
1366 | --pos; | |
1367 | if (isascii(cb.CharAt(pos))) | |
1368 | ++pos; | |
1369 | } else { | |
1370 | ++pos; | |
65ec6247 RD |
1371 | } |
1372 | } | |
1373 | } | |
1374 | return pos; | |
1375 | } | |
1376 | ||
1377 | int Document::WordPartRight(int pos) { | |
1378 | char startChar = cb.CharAt(pos); | |
1379 | int length = Length(); | |
1380 | if (IsWordPartSeparator(startChar)) { | |
1381 | while (pos < length && IsWordPartSeparator(cb.CharAt(pos))) | |
1382 | ++pos; | |
1383 | startChar = cb.CharAt(pos); | |
1384 | } | |
9e730a78 RD |
1385 | if (!isascii(startChar)) { |
1386 | while (pos < length && !isascii(cb.CharAt(pos))) | |
65ec6247 | 1387 | ++pos; |
9e730a78 RD |
1388 | } else if (IsLowerCase(startChar)) { |
1389 | while (pos < length && IsLowerCase(cb.CharAt(pos))) | |
65ec6247 | 1390 | ++pos; |
9e730a78 RD |
1391 | } else if (IsUpperCase(startChar)) { |
1392 | if (IsLowerCase(cb.CharAt(pos + 1))) { | |
1393 | ++pos; | |
1394 | while (pos < length && IsLowerCase(cb.CharAt(pos))) | |
65ec6247 RD |
1395 | ++pos; |
1396 | } else { | |
9e730a78 | 1397 | while (pos < length && IsUpperCase(cb.CharAt(pos))) |
65ec6247 RD |
1398 | ++pos; |
1399 | } | |
9e730a78 | 1400 | if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1))) |
65ec6247 | 1401 | --pos; |
9e730a78 RD |
1402 | } else if (IsADigit(startChar)) { |
1403 | while (pos < length && IsADigit(cb.CharAt(pos))) | |
65ec6247 | 1404 | ++pos; |
9e730a78 RD |
1405 | } else if (IsPunctuation(startChar)) { |
1406 | while (pos < length && IsPunctuation(cb.CharAt(pos))) | |
65ec6247 RD |
1407 | ++pos; |
1408 | } else if (isspacechar(startChar)) { | |
1409 | while (pos < length && isspacechar(cb.CharAt(pos))) | |
1410 | ++pos; | |
9e730a78 RD |
1411 | } else { |
1412 | ++pos; | |
1413 | } | |
1414 | return pos; | |
1415 | } | |
1416 | ||
8e54aaed RD |
1417 | bool IsLineEndChar(char c) { |
1418 | return (c == '\n' || c == '\r'); | |
1419 | } | |
1420 | ||
1421 | int Document::ExtendStyleRange(int pos, int delta, bool singleLine) { | |
9e730a78 RD |
1422 | int sStart = cb.StyleAt(pos); |
1423 | if (delta < 0) { | |
8e54aaed | 1424 | while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) ) |
9e730a78 RD |
1425 | pos--; |
1426 | pos++; | |
1427 | } else { | |
8e54aaed | 1428 | while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) ) |
9e730a78 | 1429 | pos++; |
65ec6247 RD |
1430 | } |
1431 | return pos; | |
1432 | } |