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