]>
Commit | Line | Data |
---|---|---|
9ce192d4 RD |
1 | // Scintilla source code edit control |
2 | // Document.cxx - text document that handles notifications, DBCS, styling, words and end of line | |
3 | // Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> | |
4 | // The License.txt file describes the conditions under which this software may be distributed. | |
5 | ||
6 | #include <stdlib.h> | |
7 | #include <string.h> | |
8 | #include <stdio.h> | |
9 | #include <ctype.h> | |
10 | ||
11 | #include "Platform.h" | |
12 | ||
13 | #include "Scintilla.h" | |
14 | #include "SVector.h" | |
15 | #include "CellBuffer.h" | |
16 | #include "Document.h" | |
17 | ||
18 | Document::Document() { | |
19 | refCount = 0; | |
20 | #ifdef unix | |
21 | eolMode = SC_EOL_LF; | |
22 | #else | |
23 | eolMode = SC_EOL_CRLF; | |
24 | #endif | |
25 | dbcsCodePage = 0; | |
26 | stylingBits = 5; | |
27 | stylingBitsMask = 0x1F; | |
28 | stylingPos = 0; | |
29 | stylingMask = 0; | |
30 | for (int ch = 0; ch < 256; ch++) { | |
31 | wordchars[ch] = isalnum(ch) || ch == '_'; | |
32 | } | |
33 | endStyled = 0; | |
34 | enteredCount = 0; | |
f6bcfd97 | 35 | enteredReadOnlyCount = 0; |
9ce192d4 | 36 | tabInChars = 8; |
f6bcfd97 BP |
37 | indentInChars = 0; |
38 | useTabs = true; | |
9ce192d4 RD |
39 | watchers = 0; |
40 | lenWatchers = 0; | |
41 | } | |
42 | ||
43 | Document::~Document() { | |
44 | for (int i = 0; i < lenWatchers; i++) { | |
45 | watchers[i].watcher->NotifyDeleted(this, watchers[i].userData); | |
46 | } | |
47 | delete []watchers; | |
48 | watchers = 0; | |
49 | lenWatchers = 0; | |
50 | } | |
51 | ||
52 | // Increase reference count and return its previous value. | |
53 | int Document::AddRef() { | |
54 | return refCount++; | |
55 | } | |
56 | ||
d134f170 | 57 | // Decrease reference count and return its previous value. |
9ce192d4 RD |
58 | // Delete the document if reference count reaches zero. |
59 | int Document::Release() { | |
60 | int curRefCount = --refCount; | |
61 | if (curRefCount == 0) | |
62 | delete this; | |
63 | return curRefCount; | |
64 | } | |
65 | ||
66 | void Document::SetSavePoint() { | |
67 | cb.SetSavePoint(); | |
68 | NotifySavePoint(true); | |
69 | } | |
70 | ||
f6bcfd97 BP |
71 | int Document::AddMark(int line, int markerNum) { |
72 | int prev = cb.AddMark(line, markerNum); | |
73 | DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0); | |
74 | NotifyModified(mh); | |
75 | return prev; | |
64a3ee5f ES |
76 | } |
77 | ||
f6bcfd97 BP |
78 | void Document::DeleteMark(int line, int markerNum) { |
79 | cb.DeleteMark(line, markerNum); | |
80 | DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0); | |
81 | NotifyModified(mh); | |
88b780d9 RD |
82 | } |
83 | ||
f6bcfd97 BP |
84 | void Document::DeleteMarkFromHandle(int markerHandle) { |
85 | cb.DeleteMarkFromHandle(markerHandle); | |
86 | DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0); | |
87 | NotifyModified(mh); | |
88 | } | |
89 | ||
90 | void Document::DeleteAllMarks(int markerNum) { | |
91 | cb.DeleteAllMarks(markerNum); | |
92 | DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0); | |
93 | NotifyModified(mh); | |
94 | } | |
95 | ||
96 | int Document::LineStart(int line) { | |
97 | return cb.LineStart(line); | |
98 | } | |
99 | ||
100 | int Document::LineEnd(int line) { | |
9ce192d4 | 101 | if (line == LinesTotal() - 1) { |
f6bcfd97 | 102 | return LineStart(line + 1); |
9ce192d4 | 103 | } else { |
f6bcfd97 | 104 | int position = LineStart(line + 1) - 1; |
9ce192d4 RD |
105 | // When line terminator is CR+LF, may need to go back one more |
106 | if ((position > LineStart(line)) && (cb.CharAt(position - 1) == '\r')) { | |
107 | position--; | |
108 | } | |
f6bcfd97 | 109 | return position; |
9ce192d4 | 110 | } |
f6bcfd97 BP |
111 | } |
112 | ||
113 | int Document::LineFromPosition(int pos) { | |
114 | return cb.LineFromPosition(pos); | |
115 | } | |
116 | ||
117 | int Document::LineEndPosition(int position) { | |
118 | return LineEnd(LineFromPosition(position)); | |
9ce192d4 RD |
119 | } |
120 | ||
121 | int Document::VCHomePosition(int position) { | |
122 | int line = LineFromPosition(position); | |
123 | int startPosition = LineStart(line); | |
124 | int endLine = LineStart(line + 1) - 1; | |
125 | int startText = startPosition; | |
126 | while (startText < endLine && (cb.CharAt(startText) == ' ' || cb.CharAt(startText) == '\t' ) ) | |
127 | startText++; | |
128 | if (position == startText) | |
129 | return startPosition; | |
130 | else | |
131 | return startText; | |
132 | } | |
133 | ||
134 | int Document::SetLevel(int line, int level) { | |
135 | int prev = cb.SetLevel(line, level); | |
136 | if (prev != level) { | |
137 | DocModification mh(SC_MOD_CHANGEFOLD, LineStart(line), 0, 0, 0); | |
138 | mh.line = line; | |
139 | mh.foldLevelNow = level; | |
140 | mh.foldLevelPrev = prev; | |
141 | NotifyModified(mh); | |
142 | } | |
143 | return prev; | |
144 | } | |
145 | ||
146 | static bool IsSubordinate(int levelStart, int levelTry) { | |
147 | if (levelTry & SC_FOLDLEVELWHITEFLAG) | |
148 | return true; | |
149 | else | |
150 | return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK); | |
151 | } | |
152 | ||
153 | int Document::GetLastChild(int lineParent, int level) { | |
154 | if (level == -1) | |
155 | level = GetLevel(lineParent) & SC_FOLDLEVELNUMBERMASK; | |
156 | int maxLine = LinesTotal(); | |
157 | int lineMaxSubord = lineParent; | |
f6bcfd97 BP |
158 | while (lineMaxSubord < maxLine-1) { |
159 | EnsureStyledTo(LineStart(lineMaxSubord+2)); | |
160 | if (!IsSubordinate(level, GetLevel(lineMaxSubord+1))) | |
161 | break; | |
9ce192d4 RD |
162 | lineMaxSubord++; |
163 | } | |
164 | if (lineMaxSubord > lineParent) { | |
165 | if (level > (GetLevel(lineMaxSubord+1) & SC_FOLDLEVELNUMBERMASK)) { | |
166 | // Have chewed up some whitespace that belongs to a parent so seek back | |
167 | if ((lineMaxSubord > lineParent) && (GetLevel(lineMaxSubord) & SC_FOLDLEVELWHITEFLAG)) { | |
168 | lineMaxSubord--; | |
169 | } | |
170 | } | |
171 | } | |
172 | return lineMaxSubord; | |
173 | } | |
174 | ||
175 | int Document::GetFoldParent(int line) { | |
176 | int level = GetLevel(line); | |
177 | int lineLook = line-1; | |
178 | while ((lineLook > 0) && ( | |
179 | (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) || | |
180 | ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level)) | |
181 | ) { | |
182 | lineLook--; | |
183 | } | |
184 | if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) && | |
185 | ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) { | |
186 | return lineLook; | |
187 | } else { | |
188 | return -1; | |
189 | } | |
190 | } | |
191 | ||
192 | int Document::ClampPositionIntoDocument(int pos) { | |
193 | return Platform::Clamp(pos, 0, Length()); | |
194 | } | |
195 | ||
196 | bool Document::IsCrLf(int pos) { | |
197 | if (pos < 0) | |
198 | return false; | |
199 | if (pos >= (Length() - 1)) | |
200 | return false; | |
201 | return (cb.CharAt(pos) == '\r') && (cb.CharAt(pos + 1) == '\n'); | |
202 | } | |
203 | ||
9ce192d4 | 204 | #if PLAT_WIN |
d134f170 | 205 | bool Document::IsDBCS(int pos) { |
9ce192d4 | 206 | if (dbcsCodePage) { |
f6bcfd97 BP |
207 | if (SC_CP_UTF8 == dbcsCodePage) { |
208 | unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos)); | |
209 | return ch >= 0x80; | |
210 | } else { | |
211 | // Anchor DBCS calculations at start of line because start of line can | |
212 | // not be a DBCS trail byte. | |
213 | int startLine = pos; | |
214 | while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n') | |
215 | startLine--; | |
216 | while (startLine <= pos) { | |
217 | if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) { | |
218 | startLine++; | |
219 | if (startLine >= pos) | |
220 | return true; | |
221 | } | |
9ce192d4 | 222 | startLine++; |
9ce192d4 | 223 | } |
9ce192d4 RD |
224 | } |
225 | } | |
226 | return false; | |
d134f170 | 227 | } |
9ce192d4 | 228 | #else |
d134f170 RD |
229 | // PLAT_GTK or PLAT_WX |
230 | // TODO: support DBCS under GTK+ and WX | |
231 | bool Document::IsDBCS(int) { | |
9ce192d4 | 232 | return false; |
9ce192d4 | 233 | } |
d134f170 | 234 | #endif |
9ce192d4 | 235 | |
f6bcfd97 BP |
236 | int Document::LenChar(int pos) { |
237 | if (IsCrLf(pos)) { | |
238 | return 2; | |
239 | } else if (SC_CP_UTF8 == dbcsCodePage) { | |
240 | unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos)); | |
241 | if (ch < 0x80) | |
242 | return 1; | |
243 | int len = 2; | |
244 | if (ch >= (0x80+0x40+0x20)) | |
245 | len = 3; | |
246 | int lengthDoc = Length(); | |
247 | if ((pos + len) > lengthDoc) | |
248 | return lengthDoc-pos; | |
249 | else | |
250 | return len; | |
251 | } else if (IsDBCS(pos)) { | |
252 | return 2; | |
253 | } else { | |
254 | return 1; | |
255 | } | |
256 | } | |
257 | ||
9ce192d4 RD |
258 | // Normalise a position so that it is not halfway through a two byte character. |
259 | // This can occur in two situations - | |
260 | // When lines are terminated with \r\n pairs which should be treated as one character. | |
261 | // When displaying DBCS text such as Japanese. | |
262 | // If moving, move the position in the indicated direction. | |
263 | int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) { | |
264 | //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir); | |
265 | // If out of range, just return value - should be fixed up after | |
266 | if (pos < 0) | |
267 | return pos; | |
268 | if (pos > Length()) | |
269 | return pos; | |
270 | ||
271 | // Position 0 and Length() can not be between any two characters | |
272 | if (pos == 0) | |
273 | return pos; | |
274 | if (pos == Length()) | |
275 | return pos; | |
276 | ||
277 | // assert pos > 0 && pos < Length() | |
278 | if (checkLineEnd && IsCrLf(pos - 1)) { | |
279 | if (moveDir > 0) | |
280 | return pos + 1; | |
281 | else | |
282 | return pos - 1; | |
283 | } | |
284 | ||
285 | // Not between CR and LF | |
286 | ||
287 | #if PLAT_WIN | |
288 | if (dbcsCodePage) { | |
f6bcfd97 BP |
289 | if (SC_CP_UTF8 == dbcsCodePage) { |
290 | unsigned char ch = static_cast<unsigned char>(cb.CharAt(pos)); | |
291 | while ((pos > 0) && (pos < Length()) && (ch >= 0x80) && (ch < (0x80 + 0x40))) { | |
292 | // ch is a trail byte | |
293 | if (moveDir > 0) | |
294 | pos++; | |
295 | else | |
296 | pos--; | |
297 | ch = static_cast<unsigned char>(cb.CharAt(pos)); | |
298 | } | |
299 | } else { | |
300 | // Anchor DBCS calculations at start of line because start of line can | |
301 | // not be a DBCS trail byte. | |
302 | int startLine = pos; | |
303 | while (startLine > 0 && cb.CharAt(startLine) != '\r' && cb.CharAt(startLine) != '\n') | |
304 | startLine--; | |
305 | bool atLeadByte = false; | |
306 | while (startLine < pos) { | |
307 | if (atLeadByte) | |
308 | atLeadByte = false; | |
309 | else if (IsDBCSLeadByteEx(dbcsCodePage, cb.CharAt(startLine))) | |
310 | atLeadByte = true; | |
311 | else | |
312 | atLeadByte = false; | |
313 | startLine++; | |
314 | //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-"); | |
315 | } | |
9ce192d4 | 316 | |
f6bcfd97 BP |
317 | if (atLeadByte) { |
318 | // Position is between a lead byte and a trail byte | |
319 | if (moveDir > 0) | |
320 | return pos + 1; | |
321 | else | |
322 | return pos - 1; | |
323 | } | |
9ce192d4 RD |
324 | } |
325 | } | |
326 | #endif | |
327 | ||
328 | return pos; | |
329 | } | |
330 | ||
331 | void Document::ModifiedAt(int pos) { | |
332 | if (endStyled > pos) | |
333 | endStyled = pos; | |
334 | } | |
335 | ||
336 | // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt. | |
337 | // SetStyleAt does not change the persistent state of a document | |
338 | ||
339 | // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number | |
340 | void Document::DeleteChars(int pos, int len) { | |
d134f170 RD |
341 | if ((pos + len) > Length()) |
342 | return; | |
f6bcfd97 BP |
343 | if (cb.IsReadOnly() && enteredReadOnlyCount==0) { |
344 | enteredReadOnlyCount++; | |
345 | NotifyModifyAttempt(); | |
346 | enteredReadOnlyCount--; | |
347 | } | |
9ce192d4 RD |
348 | if (enteredCount == 0) { |
349 | enteredCount++; | |
9ce192d4 | 350 | if (!cb.IsReadOnly()) { |
f6bcfd97 BP |
351 | NotifyModified( |
352 | DocModification( | |
353 | SC_MOD_BEFOREDELETE | SC_PERFORMED_USER, | |
354 | pos, len, | |
355 | 0, 0)); | |
9ce192d4 RD |
356 | int prevLinesTotal = LinesTotal(); |
357 | bool startSavePoint = cb.IsSavePoint(); | |
358 | const char *text = cb.DeleteChars(pos*2, len * 2); | |
359 | if (startSavePoint && cb.IsCollectingUndo()) | |
360 | NotifySavePoint(!startSavePoint); | |
361 | ModifiedAt(pos); | |
f6bcfd97 BP |
362 | NotifyModified( |
363 | DocModification( | |
364 | SC_MOD_DELETETEXT | SC_PERFORMED_USER, | |
365 | pos, len, | |
366 | LinesTotal() - prevLinesTotal, text)); | |
9ce192d4 RD |
367 | } |
368 | enteredCount--; | |
369 | } | |
370 | } | |
371 | ||
372 | void Document::InsertStyledString(int position, char *s, int insertLength) { | |
f6bcfd97 BP |
373 | if (cb.IsReadOnly() && enteredReadOnlyCount==0) { |
374 | enteredReadOnlyCount++; | |
375 | NotifyModifyAttempt(); | |
376 | enteredReadOnlyCount--; | |
377 | } | |
9ce192d4 RD |
378 | if (enteredCount == 0) { |
379 | enteredCount++; | |
9ce192d4 | 380 | if (!cb.IsReadOnly()) { |
f6bcfd97 BP |
381 | NotifyModified( |
382 | DocModification( | |
383 | SC_MOD_BEFOREINSERT | SC_PERFORMED_USER, | |
384 | position / 2, insertLength / 2, | |
385 | 0, 0)); | |
9ce192d4 RD |
386 | int prevLinesTotal = LinesTotal(); |
387 | bool startSavePoint = cb.IsSavePoint(); | |
388 | const char *text = cb.InsertString(position, s, insertLength); | |
389 | if (startSavePoint && cb.IsCollectingUndo()) | |
390 | NotifySavePoint(!startSavePoint); | |
391 | ModifiedAt(position / 2); | |
f6bcfd97 BP |
392 | NotifyModified( |
393 | DocModification( | |
394 | SC_MOD_INSERTTEXT | SC_PERFORMED_USER, | |
395 | position / 2, insertLength / 2, | |
396 | LinesTotal() - prevLinesTotal, text)); | |
9ce192d4 RD |
397 | } |
398 | enteredCount--; | |
399 | } | |
400 | } | |
401 | ||
402 | int Document::Undo() { | |
403 | int newPos = 0; | |
404 | if (enteredCount == 0) { | |
405 | enteredCount++; | |
406 | bool startSavePoint = cb.IsSavePoint(); | |
407 | int steps = cb.StartUndo(); | |
f6bcfd97 | 408 | //Platform::DebugPrintf("Steps=%d\n", steps); |
9ce192d4 RD |
409 | for (int step=0; step<steps; step++) { |
410 | int prevLinesTotal = LinesTotal(); | |
f6bcfd97 BP |
411 | const Action &action = cb.GetUndoStep(); |
412 | if (action.at == removeAction) { | |
413 | NotifyModified(DocModification( | |
414 | SC_MOD_BEFOREINSERT | SC_PERFORMED_UNDO, action)); | |
415 | } else { | |
416 | NotifyModified(DocModification( | |
417 | SC_MOD_BEFOREDELETE | SC_PERFORMED_UNDO, action)); | |
418 | } | |
419 | cb.PerformUndoStep(); | |
9ce192d4 RD |
420 | int cellPosition = action.position / 2; |
421 | ModifiedAt(cellPosition); | |
422 | newPos = cellPosition; | |
423 | ||
424 | int modFlags = SC_PERFORMED_UNDO; | |
425 | // With undo, an insertion action becomes a deletion notification | |
426 | if (action.at == removeAction) { | |
427 | newPos += action.lenData; | |
428 | modFlags |= SC_MOD_INSERTTEXT; | |
429 | } else { | |
430 | modFlags |= SC_MOD_DELETETEXT; | |
431 | } | |
432 | if (step == steps-1) | |
433 | modFlags |= SC_LASTSTEPINUNDOREDO; | |
434 | NotifyModified(DocModification(modFlags, cellPosition, action.lenData, | |
435 | LinesTotal() - prevLinesTotal, action.data)); | |
436 | } | |
437 | ||
438 | bool endSavePoint = cb.IsSavePoint(); | |
439 | if (startSavePoint != endSavePoint) | |
440 | NotifySavePoint(endSavePoint); | |
441 | enteredCount--; | |
442 | } | |
443 | return newPos; | |
444 | } | |
445 | ||
446 | int Document::Redo() { | |
447 | int newPos = 0; | |
448 | if (enteredCount == 0) { | |
449 | enteredCount++; | |
450 | bool startSavePoint = cb.IsSavePoint(); | |
451 | int steps = cb.StartRedo(); | |
452 | for (int step=0; step<steps; step++) { | |
453 | int prevLinesTotal = LinesTotal(); | |
f6bcfd97 BP |
454 | const Action &action = cb.GetRedoStep(); |
455 | if (action.at == insertAction) { | |
456 | NotifyModified(DocModification( | |
457 | SC_MOD_BEFOREINSERT | SC_PERFORMED_REDO, action)); | |
458 | } else { | |
459 | NotifyModified(DocModification( | |
460 | SC_MOD_BEFOREDELETE | SC_PERFORMED_REDO, action)); | |
461 | } | |
462 | cb.PerformRedoStep(); | |
463 | ModifiedAt(action.position / 2); | |
464 | newPos = action.position / 2; | |
9ce192d4 RD |
465 | |
466 | int modFlags = SC_PERFORMED_REDO; | |
467 | if (action.at == insertAction) { | |
468 | newPos += action.lenData; | |
469 | modFlags |= SC_MOD_INSERTTEXT; | |
470 | } else { | |
471 | modFlags |= SC_MOD_DELETETEXT; | |
472 | } | |
473 | if (step == steps-1) | |
474 | modFlags |= SC_LASTSTEPINUNDOREDO; | |
f6bcfd97 BP |
475 | NotifyModified( |
476 | DocModification(modFlags, action.position / 2, action.lenData, | |
9ce192d4 RD |
477 | LinesTotal() - prevLinesTotal, action.data)); |
478 | } | |
479 | ||
480 | bool endSavePoint = cb.IsSavePoint(); | |
481 | if (startSavePoint != endSavePoint) | |
482 | NotifySavePoint(endSavePoint); | |
483 | enteredCount--; | |
484 | } | |
485 | return newPos; | |
486 | } | |
487 | ||
488 | void Document::InsertChar(int pos, char ch) { | |
489 | char chs[2]; | |
490 | chs[0] = ch; | |
491 | chs[1] = 0; | |
492 | InsertStyledString(pos*2, chs, 2); | |
493 | } | |
494 | ||
495 | // Insert a null terminated string | |
496 | void Document::InsertString(int position, const char *s) { | |
497 | InsertString(position, s, strlen(s)); | |
498 | } | |
499 | ||
500 | // Insert a string with a length | |
501 | void Document::InsertString(int position, const char *s, int insertLength) { | |
502 | char *sWithStyle = new char[insertLength * 2]; | |
503 | if (sWithStyle) { | |
504 | for (int i = 0; i < insertLength; i++) { | |
505 | sWithStyle[i*2] = s[i]; | |
506 | sWithStyle[i*2 + 1] = 0; | |
507 | } | |
508 | InsertStyledString(position*2, sWithStyle, insertLength*2); | |
509 | delete []sWithStyle; | |
510 | } | |
511 | } | |
512 | ||
f6bcfd97 BP |
513 | void Document::ChangeChar(int pos, char ch) { |
514 | DeleteChars(pos, 1); | |
515 | InsertChar(pos, ch); | |
516 | } | |
517 | ||
9ce192d4 | 518 | void Document::DelChar(int pos) { |
f6bcfd97 | 519 | DeleteChars(pos, LenChar(pos)); |
9ce192d4 RD |
520 | } |
521 | ||
522 | int Document::DelCharBack(int pos) { | |
523 | if (pos <= 0) { | |
524 | return pos; | |
525 | } else if (IsCrLf(pos - 2)) { | |
526 | DeleteChars(pos - 2, 2); | |
527 | return pos - 2; | |
f6bcfd97 BP |
528 | } else if (SC_CP_UTF8 == dbcsCodePage) { |
529 | int startChar = MovePositionOutsideChar(pos-1, -1, false); | |
530 | DeleteChars(startChar, pos - startChar); | |
531 | return startChar; | |
9ce192d4 RD |
532 | } else if (IsDBCS(pos - 1)) { |
533 | DeleteChars(pos - 2, 2); | |
534 | return pos - 2; | |
535 | } else { | |
536 | DeleteChars(pos - 1, 1); | |
537 | return pos - 1; | |
538 | } | |
539 | } | |
540 | ||
f6bcfd97 BP |
541 | static bool isindentchar(char ch) { |
542 | return (ch == ' ') || (ch == '\t'); | |
543 | } | |
544 | ||
545 | static int NextTab(int pos, int tabSize) { | |
546 | return ((pos / tabSize) + 1) * tabSize; | |
547 | } | |
548 | ||
549 | static void CreateIndentation(char *linebuf, int length, int indent, int tabSize, bool insertSpaces) { | |
550 | length--; // ensure space for \0 | |
551 | if (!insertSpaces) { | |
552 | while ((indent >= tabSize) && (length > 0)) { | |
553 | *linebuf++ = '\t'; | |
554 | indent -= tabSize; | |
555 | length--; | |
9ce192d4 | 556 | } |
f6bcfd97 BP |
557 | } |
558 | while ((indent > 0) && (length > 0)) { | |
559 | *linebuf++ = ' '; | |
560 | indent--; | |
561 | length--; | |
562 | } | |
563 | *linebuf = '\0'; | |
564 | } | |
565 | ||
566 | int Document::GetLineIndentation(int line) { | |
567 | int indent = 0; | |
568 | if ((line >= 0) && (line < LinesTotal())) { | |
569 | int lineStart = LineStart(line); | |
570 | int length = Length(); | |
571 | for (int i=lineStart;i<length;i++) { | |
572 | char ch = cb.CharAt(i); | |
573 | if (ch == ' ') | |
574 | indent++; | |
575 | else if (ch == '\t') | |
576 | indent = NextTab(indent, tabInChars); | |
577 | else | |
578 | return indent; | |
9ce192d4 RD |
579 | } |
580 | } | |
f6bcfd97 BP |
581 | return indent; |
582 | } | |
583 | ||
584 | void Document::SetLineIndentation(int line, int indent) { | |
585 | int indentOfLine = GetLineIndentation(line); | |
586 | if (indent < 0) | |
587 | indent = 0; | |
588 | if (indent != indentOfLine) { | |
589 | char linebuf[1000]; | |
590 | CreateIndentation(linebuf, sizeof(linebuf), indent, tabInChars, !useTabs); | |
591 | int thisLineStart = LineStart(line); | |
592 | int indentPos = GetLineIndentPosition(line); | |
593 | DeleteChars(thisLineStart, indentPos - thisLineStart); | |
594 | InsertString(thisLineStart, linebuf); | |
595 | } | |
596 | } | |
597 | ||
598 | int Document::GetLineIndentPosition(int line) { | |
d134f170 RD |
599 | if (line < 0) |
600 | return 0; | |
f6bcfd97 BP |
601 | int pos = LineStart(line); |
602 | int length = Length(); | |
603 | while ((pos < length) && isindentchar(cb.CharAt(pos))) { | |
604 | pos++; | |
605 | } | |
606 | return pos; | |
607 | } | |
608 | ||
d134f170 RD |
609 | int Document::GetColumn(int pos) { |
610 | int column = 0; | |
611 | int line = LineFromPosition(pos); | |
612 | if ((line >= 0) && (line < LinesTotal())) { | |
613 | for (int i=LineStart(line);i<pos;i++) { | |
614 | char ch = cb.CharAt(i); | |
615 | if (ch == '\t') | |
616 | column = NextTab(column, tabInChars); | |
617 | else if (ch == '\r') | |
618 | return column; | |
619 | else if (ch == '\n') | |
620 | return column; | |
621 | else | |
622 | column++; | |
623 | } | |
624 | } | |
625 | return column; | |
626 | } | |
627 | ||
f6bcfd97 BP |
628 | void Document::Indent(bool forwards, int lineBottom, int lineTop) { |
629 | // Dedent - suck white space off the front of the line to dedent by equivalent of a tab | |
630 | for (int line = lineBottom; line >= lineTop; line--) { | |
631 | int indentOfLine = GetLineIndentation(line); | |
632 | if (forwards) | |
633 | SetLineIndentation(line, indentOfLine + IndentSize()); | |
634 | else | |
635 | SetLineIndentation(line, indentOfLine - IndentSize()); | |
636 | } | |
9ce192d4 RD |
637 | } |
638 | ||
639 | void Document::ConvertLineEnds(int eolModeSet) { | |
640 | BeginUndoAction(); | |
641 | for (int pos = 0; pos < Length(); pos++) { | |
642 | if (cb.CharAt(pos) == '\r') { | |
643 | if (cb.CharAt(pos+1) == '\n') { | |
644 | if (eolModeSet != SC_EOL_CRLF) { | |
645 | DeleteChars(pos, 2); | |
646 | if (eolModeSet == SC_EOL_CR) | |
647 | InsertString(pos, "\r", 1); | |
648 | else | |
649 | InsertString(pos, "\n", 1); | |
650 | } else { | |
651 | pos++; | |
652 | } | |
653 | } else { | |
654 | if (eolModeSet != SC_EOL_CR) { | |
655 | DeleteChars(pos, 1); | |
656 | if (eolModeSet == SC_EOL_CRLF) { | |
657 | InsertString(pos, "\r\n", 2); | |
658 | pos++; | |
659 | } else { | |
660 | InsertString(pos, "\n", 1); | |
661 | } | |
662 | } | |
663 | } | |
664 | } else if (cb.CharAt(pos) == '\n') { | |
665 | if (eolModeSet != SC_EOL_LF) { | |
666 | DeleteChars(pos, 1); | |
667 | if (eolModeSet == SC_EOL_CRLF) { | |
668 | InsertString(pos, "\r\n", 2); | |
669 | pos++; | |
670 | } else { | |
671 | InsertString(pos, "\r", 1); | |
672 | } | |
673 | } | |
674 | } | |
675 | } | |
676 | EndUndoAction(); | |
677 | } | |
678 | ||
679 | bool Document::IsWordChar(unsigned char ch) { | |
f6bcfd97 BP |
680 | if ((SC_CP_UTF8 == dbcsCodePage) && (ch >0x80)) |
681 | return true; | |
9ce192d4 RD |
682 | return wordchars[ch]; |
683 | } | |
684 | ||
685 | int Document::ExtendWordSelect(int pos, int delta) { | |
686 | if (delta < 0) { | |
687 | while (pos > 0 && IsWordChar(cb.CharAt(pos - 1))) | |
688 | pos--; | |
689 | } else { | |
690 | while (pos < (Length()) && IsWordChar(cb.CharAt(pos))) | |
691 | pos++; | |
692 | } | |
693 | return pos; | |
694 | } | |
695 | ||
696 | int Document::NextWordStart(int pos, int delta) { | |
697 | if (delta < 0) { | |
698 | while (pos > 0 && (cb.CharAt(pos - 1) == ' ' || cb.CharAt(pos - 1) == '\t')) | |
699 | pos--; | |
700 | if (isspace(cb.CharAt(pos - 1))) { // Back up to previous line | |
701 | while (pos > 0 && isspace(cb.CharAt(pos - 1))) | |
702 | pos--; | |
703 | } else { | |
704 | bool startAtWordChar = IsWordChar(cb.CharAt(pos - 1)); | |
705 | while (pos > 0 && !isspace(cb.CharAt(pos - 1)) && (startAtWordChar == IsWordChar(cb.CharAt(pos - 1)))) | |
706 | pos--; | |
707 | } | |
708 | } else { | |
709 | bool startAtWordChar = IsWordChar(cb.CharAt(pos)); | |
710 | while (pos < (Length()) && isspace(cb.CharAt(pos))) | |
711 | pos++; | |
712 | while (pos < (Length()) && !isspace(cb.CharAt(pos)) && (startAtWordChar == IsWordChar(cb.CharAt(pos)))) | |
713 | pos++; | |
714 | while (pos < (Length()) && (cb.CharAt(pos) == ' ' || cb.CharAt(pos) == '\t')) | |
715 | pos++; | |
716 | } | |
717 | return pos; | |
718 | } | |
719 | ||
d134f170 RD |
720 | bool Document::IsWordStartAt(int pos) { |
721 | if (pos > 0) { | |
722 | return !IsWordChar(CharAt(pos - 1)); | |
9ce192d4 | 723 | } |
d134f170 RD |
724 | return true; |
725 | } | |
726 | ||
727 | bool Document::IsWordEndAt(int pos) { | |
728 | if (pos < Length() - 1) { | |
729 | return !IsWordChar(CharAt(pos)); | |
9ce192d4 RD |
730 | } |
731 | return true; | |
732 | } | |
733 | ||
d134f170 RD |
734 | bool Document::IsWordAt(int start, int end) { |
735 | return IsWordStartAt(start) && IsWordEndAt(end); | |
736 | } | |
737 | ||
9ce192d4 RD |
738 | // Find text in document, supporting both forward and backward |
739 | // searches (just pass minPos > maxPos to do a backward search) | |
740 | // Has not been tested with backwards DBCS searches yet. | |
d134f170 RD |
741 | long Document::FindText(int minPos, int maxPos, const char *s, |
742 | bool caseSensitive, bool word, bool wordStart) { | |
9ce192d4 RD |
743 | bool forward = minPos <= maxPos; |
744 | int increment = forward ? 1 : -1; | |
745 | ||
746 | // Range endpoints should not be inside DBCS characters, but just in case, move them. | |
747 | int startPos = MovePositionOutsideChar(minPos, increment, false); | |
748 | int endPos = MovePositionOutsideChar(maxPos, increment, false); | |
749 | ||
750 | // Compute actual search ranges needed | |
751 | int lengthFind = strlen(s); | |
f6bcfd97 | 752 | int endSearch = endPos; |
9ce192d4 RD |
753 | if (startPos <= endPos) { |
754 | endSearch = endPos - lengthFind + 1; | |
9ce192d4 RD |
755 | } |
756 | //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind); | |
757 | char firstChar = s[0]; | |
758 | if (!caseSensitive) | |
f6bcfd97 | 759 | firstChar = static_cast<char>(toupper(firstChar)); |
9ce192d4 RD |
760 | int pos = startPos; |
761 | while (forward ? (pos < endSearch) : (pos >= endSearch)) { | |
762 | char ch = CharAt(pos); | |
763 | if (caseSensitive) { | |
764 | if (ch == firstChar) { | |
765 | bool found = true; | |
766 | for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) { | |
767 | ch = CharAt(pos + posMatch); | |
768 | if (ch != s[posMatch]) | |
769 | found = false; | |
770 | } | |
771 | if (found) { | |
d134f170 RD |
772 | if ((!word && !wordStart) || |
773 | word && IsWordAt(pos, pos + lengthFind) || | |
774 | wordStart && IsWordStartAt(pos)) | |
775 | return pos; | |
9ce192d4 RD |
776 | } |
777 | } | |
778 | } else { | |
779 | if (toupper(ch) == firstChar) { | |
780 | bool found = true; | |
781 | for (int posMatch = 1; posMatch < lengthFind && found; posMatch++) { | |
782 | ch = CharAt(pos + posMatch); | |
783 | if (toupper(ch) != toupper(s[posMatch])) | |
784 | found = false; | |
785 | } | |
786 | if (found) { | |
d134f170 RD |
787 | if (!(word && wordStart) || |
788 | word && IsWordAt(pos, pos + lengthFind) || | |
789 | wordStart && IsWordStartAt(pos)) | |
790 | return pos; | |
9ce192d4 RD |
791 | } |
792 | } | |
793 | } | |
794 | pos += increment; | |
795 | if (dbcsCodePage) { | |
796 | // Ensure trying to match from start of character | |
797 | pos = MovePositionOutsideChar(pos, increment, false); | |
798 | } | |
799 | } | |
800 | //Platform::DebugPrintf("Not found\n"); | |
801 | return - 1; | |
802 | } | |
803 | ||
804 | int Document::LinesTotal() { | |
805 | return cb.Lines(); | |
806 | } | |
807 | ||
f6bcfd97 BP |
808 | void Document::ChangeCase(Range r, bool makeUpperCase) { |
809 | for (int pos=r.start; pos<r.end; pos++) { | |
810 | char ch = CharAt(pos); | |
811 | if (dbcsCodePage && IsDBCS(pos)) { | |
812 | pos += LenChar(pos); | |
813 | } else { | |
814 | if (makeUpperCase) { | |
815 | if (islower(ch)) { | |
816 | ChangeChar(pos, static_cast<char>(toupper(ch))); | |
817 | } | |
818 | } else { | |
819 | if (isupper(ch)) { | |
820 | ChangeChar(pos, static_cast<char>(tolower(ch))); | |
821 | } | |
822 | } | |
823 | } | |
824 | } | |
825 | } | |
826 | ||
9ce192d4 RD |
827 | void Document::SetWordChars(unsigned char *chars) { |
828 | int ch; | |
829 | for (ch = 0; ch < 256; ch++) { | |
830 | wordchars[ch] = false; | |
831 | } | |
832 | if (chars) { | |
833 | while (*chars) { | |
834 | wordchars[*chars] = true; | |
835 | chars++; | |
836 | } | |
837 | } else { | |
838 | for (ch = 0; ch < 256; ch++) { | |
839 | wordchars[ch] = isalnum(ch) || ch == '_'; | |
840 | } | |
841 | } | |
842 | } | |
843 | ||
844 | void Document::SetStylingBits(int bits) { | |
845 | stylingBits = bits; | |
846 | stylingBitsMask = 0; | |
847 | for (int bit=0; bit<stylingBits; bit++) { | |
848 | stylingBitsMask <<= 1; | |
849 | stylingBitsMask |= 1; | |
850 | } | |
851 | } | |
852 | ||
853 | void Document::StartStyling(int position, char mask) { | |
854 | stylingPos = position; | |
855 | stylingMask = mask; | |
856 | } | |
857 | ||
858 | void Document::SetStyleFor(int length, char style) { | |
859 | if (enteredCount == 0) { | |
860 | enteredCount++; | |
861 | int prevEndStyled = endStyled; | |
862 | if (cb.SetStyleFor(stylingPos, length, style, stylingMask)) { | |
863 | DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER, | |
864 | prevEndStyled, length); | |
865 | NotifyModified(mh); | |
866 | } | |
867 | stylingPos += length; | |
868 | endStyled = stylingPos; | |
869 | enteredCount--; | |
870 | } | |
871 | } | |
872 | ||
873 | void Document::SetStyles(int length, char *styles) { | |
874 | if (enteredCount == 0) { | |
875 | enteredCount++; | |
876 | int prevEndStyled = endStyled; | |
877 | bool didChange = false; | |
878 | for (int iPos = 0; iPos < length; iPos++, stylingPos++) { | |
879 | if (cb.SetStyleAt(stylingPos, styles[iPos], stylingMask)) { | |
880 | didChange = true; | |
881 | } | |
882 | } | |
883 | endStyled = stylingPos; | |
884 | if (didChange) { | |
885 | DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER, | |
886 | prevEndStyled, endStyled - prevEndStyled); | |
887 | NotifyModified(mh); | |
888 | } | |
889 | enteredCount--; | |
890 | } | |
891 | } | |
892 | ||
f6bcfd97 BP |
893 | bool Document::EnsureStyledTo(int pos) { |
894 | // Ask the watchers to style, and stop as soon as one responds. | |
895 | for (int i = 0; pos > GetEndStyled() && i < lenWatchers; i++) | |
896 | watchers[i].watcher->NotifyStyleNeeded(this, watchers[i].userData, pos); | |
897 | return pos <= GetEndStyled(); | |
898 | } | |
899 | ||
9ce192d4 RD |
900 | bool Document::AddWatcher(DocWatcher *watcher, void *userData) { |
901 | for (int i = 0; i < lenWatchers; i++) { | |
902 | if ((watchers[i].watcher == watcher) && | |
903 | (watchers[i].userData == userData)) | |
904 | return false; | |
905 | } | |
906 | WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers + 1]; | |
907 | if (!pwNew) | |
908 | return false; | |
909 | for (int j = 0; j < lenWatchers; j++) | |
910 | pwNew[j] = watchers[j]; | |
911 | pwNew[lenWatchers].watcher = watcher; | |
912 | pwNew[lenWatchers].userData = userData; | |
913 | delete []watchers; | |
914 | watchers = pwNew; | |
915 | lenWatchers++; | |
916 | return true; | |
917 | } | |
918 | ||
919 | bool Document::RemoveWatcher(DocWatcher *watcher, void *userData) { | |
920 | for (int i = 0; i < lenWatchers; i++) { | |
921 | if ((watchers[i].watcher == watcher) && | |
922 | (watchers[i].userData == userData)) { | |
923 | if (lenWatchers == 1) { | |
924 | delete []watchers; | |
925 | watchers = 0; | |
926 | lenWatchers = 0; | |
927 | } else { | |
928 | WatcherWithUserData *pwNew = new WatcherWithUserData[lenWatchers]; | |
929 | if (!pwNew) | |
930 | return false; | |
931 | for (int j = 0; j < lenWatchers - 1; j++) { | |
932 | pwNew[j] = (j < i) ? watchers[j] : watchers[j + 1]; | |
933 | } | |
934 | delete []watchers; | |
935 | watchers = pwNew; | |
936 | lenWatchers--; | |
937 | } | |
938 | return true; | |
939 | } | |
940 | } | |
941 | return false; | |
942 | } | |
943 | ||
944 | void Document::NotifyModifyAttempt() { | |
945 | for (int i = 0; i < lenWatchers; i++) { | |
946 | watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData); | |
947 | } | |
948 | } | |
949 | ||
950 | void Document::NotifySavePoint(bool atSavePoint) { | |
951 | for (int i = 0; i < lenWatchers; i++) { | |
952 | watchers[i].watcher->NotifySavePoint(this, watchers[i].userData, atSavePoint); | |
953 | } | |
954 | } | |
955 | ||
956 | void Document::NotifyModified(DocModification mh) { | |
957 | for (int i = 0; i < lenWatchers; i++) { | |
958 | watchers[i].watcher->NotifyModified(this, mh, watchers[i].userData); | |
959 | } | |
960 | } |