1 // Scintilla source code edit control
2 /** @file Document.cxx
3  ** Text document that handles notifications, DBCS, styling, words and end of line.
4  **/
5 // Copyright 1998-2003 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
7 
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <ctype.h>
12 
13 #include "Platform.h"
14 
15 #include "Scintilla.h"
16 #include "SVector.h"
17 #include "CellBuffer.h"
18 #include "CharClassify.h"
19 #include "Document.h"
20 #include "RESearch.h"
21 
22 // This is ASCII specific but is safe with chars >= 0x80
isspacechar(unsigned char ch)23 static inline bool isspacechar(unsigned char ch) {
24 	return (ch == ' ') || ((ch >= 0x09) && (ch <= 0x0d));
25 }
26 
IsPunctuation(char ch)27 static inline bool IsPunctuation(char ch) {
28 	return isascii(ch) && ispunct(ch);
29 }
30 
IsADigit(char ch)31 static inline bool IsADigit(char ch) {
32 	return isascii(ch) && isdigit(ch);
33 }
34 
IsLowerCase(char ch)35 static inline bool IsLowerCase(char ch) {
36 	return isascii(ch) && islower(ch);
37 }
38 
IsUpperCase(char ch)39 static inline bool IsUpperCase(char ch) {
40 	return isascii(ch) && isupper(ch);
41 }
42 
Document()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;
53 	stylingMask = 0;
54 	endStyled = 0;
55 	styleClock = 0;
56 	enteredCount = 0;
57 	enteredReadOnlyCount = 0;
58 	tabInChars = 8;
59 	indentInChars = 0;
60 	actualIndentInChars = 8;
61 	useTabs = true;
62 	tabIndents = true;
63 	backspaceUnindents = false;
64 	watchers = 0;
65 	lenWatchers = 0;
66 
67 	matchesValid = false;
68 	pre = 0;
69 	substituted = 0;
70 }
71 
~Document()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;
79 	delete pre;
80 	pre = 0;
81 	delete []substituted;
82 	substituted = 0;
83 }
84 
85 // Increase reference count and return its previous value.
AddRef()86 int Document::AddRef() {
87 	return refCount++;
88 }
89 
90 // Decrease reference count and return its previous value.
91 // Delete the document if reference count reaches zero.
Release()92 int Document::Release() {
93 	int curRefCount = --refCount;
94 	if (curRefCount == 0)
95 		delete this;
96 	return curRefCount;
97 }
98 
SetSavePoint()99 void Document::SetSavePoint() {
100 	cb.SetSavePoint();
101 	NotifySavePoint(true);
102 }
103 
AddMark(int line,int markerNum)104 int Document::AddMark(int line, int markerNum) {
105 	int prev = cb.AddMark(line, markerNum);
106 	DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
107 	mh.line = line;
108 	NotifyModified(mh);
109 	return prev;
110 }
111 
AddMarkSet(int line,int valueSet)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 
DeleteMark(int line,int markerNum)122 void Document::DeleteMark(int line, int markerNum) {
123 	cb.DeleteMark(line, markerNum);
124 	DocModification mh(SC_MOD_CHANGEMARKER, LineStart(line), 0, 0, 0, line);
125 	mh.line = line;
126 	NotifyModified(mh);
127 }
128 
DeleteMarkFromHandle(int markerHandle)129 void Document::DeleteMarkFromHandle(int markerHandle) {
130 	cb.DeleteMarkFromHandle(markerHandle);
131 	DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
132 	mh.line = -1;
133 	NotifyModified(mh);
134 }
135 
DeleteAllMarks(int markerNum)136 void Document::DeleteAllMarks(int markerNum) {
137 	cb.DeleteAllMarks(markerNum);
138 	DocModification mh(SC_MOD_CHANGEMARKER, 0, 0, 0, 0);
139 	mh.line = -1;
140 	NotifyModified(mh);
141 }
142 
LineStart(int line)143 int Document::LineStart(int line) {
144 	return cb.LineStart(line);
145 }
146 
LineEnd(int line)147 int Document::LineEnd(int line) {
148 	if (line == LinesTotal() - 1) {
149 		return LineStart(line + 1);
150 	} else {
151 		int position = LineStart(line + 1) - 1;
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 		}
156 		return position;
157 	}
158 }
159 
LineFromPosition(int pos)160 int Document::LineFromPosition(int pos) {
161 	return cb.LineFromPosition(pos);
162 }
163 
LineEndPosition(int position)164 int Document::LineEndPosition(int position) {
165 	return LineEnd(LineFromPosition(position));
166 }
167 
VCHomePosition(int position)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 
SetLevel(int line,int level)181 int Document::SetLevel(int line, int level) {
182 	int prev = cb.SetLevel(line, level);
183 	if (prev != level) {
184 		DocModification mh(SC_MOD_CHANGEFOLD | SC_MOD_CHANGEMARKER,
185 		                   LineStart(line), 0, 0, 0);
186 		mh.line = line;
187 		mh.foldLevelNow = level;
188 		mh.foldLevelPrev = prev;
189 		NotifyModified(mh);
190 	}
191 	return prev;
192 }
193 
IsSubordinate(int levelStart,int levelTry)194 static bool IsSubordinate(int levelStart, int levelTry) {
195 	if (levelTry & SC_FOLDLEVELWHITEFLAG)
196 		return true;
197 	else
198 		return (levelStart & SC_FOLDLEVELNUMBERMASK) < (levelTry & SC_FOLDLEVELNUMBERMASK);
199 }
200 
GetLastChild(int lineParent,int level)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;
206 	while (lineMaxSubord < maxLine - 1) {
207 		EnsureStyledTo(LineStart(lineMaxSubord + 2));
208 		if (!IsSubordinate(level, GetLevel(lineMaxSubord + 1)))
209 			break;
210 		lineMaxSubord++;
211 	}
212 	if (lineMaxSubord > lineParent) {
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) {
216 				lineMaxSubord--;
217 			}
218 		}
219 	}
220 	return lineMaxSubord;
221 }
222 
GetFoldParent(int line)223 int Document::GetFoldParent(int line) {
224 	int level = GetLevel(line) & SC_FOLDLEVELNUMBERMASK;
225 	int lineLook = line - 1;
226 	while ((lineLook > 0) && (
227 	            (!(GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG)) ||
228 	            ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) >= level))
229 	      ) {
230 		lineLook--;
231 	}
232 	if ((GetLevel(lineLook) & SC_FOLDLEVELHEADERFLAG) &&
233 	        ((GetLevel(lineLook) & SC_FOLDLEVELNUMBERMASK) < level)) {
234 		return lineLook;
235 	} else {
236 		return -1;
237 	}
238 }
239 
ClampPositionIntoDocument(int pos)240 int Document::ClampPositionIntoDocument(int pos) {
241 	return Platform::Clamp(pos, 0, Length());
242 }
243 
IsCrLf(int pos)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 
252 static const int maxBytesInDBCSCharacter=5;
253 
LenChar(int pos)254 int Document::LenChar(int pos) {
255 	if (pos < 0) {
256 		return 1;
257 	} else if (IsCrLf(pos)) {
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;
264 		if (ch >= (0x80 + 0x40 + 0x20))
265 			len = 3;
266 		int lengthDoc = Length();
267 		if ((pos + len) > lengthDoc)
268 			return lengthDoc -pos;
269 		else
270 			return len;
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);
279 	} else {
280 		return 1;
281 	}
282 }
283 
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.
MovePositionOutsideChar(int pos,int moveDir,bool checkLineEnd)289 int Document::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) {
290 	//Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
291 	// If out of range, just return minimum/maximum value.
292 	if (pos <= 0)
293 		return 0;
294 	if (pos >= Length())
295 		return Length();
296 
297 	// PLATFORM_ASSERT(pos > 0 && pos < Length());
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 
307 	if (dbcsCodePage) {
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++;
314 				else
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.
321 			int posCheck = LineStart(LineFromPosition(pos));
322 			while (posCheck < pos) {
323 				char mbstr[maxBytesInDBCSCharacter+1];
324 				int i;
325 				for(i=0;i<Platform::DBCSCharMaxLength();i++) {
326 					mbstr[i] = cb.CharAt(posCheck+i);
327 				}
328 				mbstr[i] = '\0';
329 
330 				int mbsize = Platform::DBCSCharLength(dbcsCodePage, mbstr);
331 				if (posCheck + mbsize == pos) {
332 					return pos;
333 				} else if (posCheck + mbsize > pos) {
334 					if (moveDir > 0) {
335 						return posCheck + mbsize;
336 					} else {
337 						return posCheck;
338 					}
339 				}
340 				posCheck += mbsize;
341 			}
342 		}
343 	}
344 
345 	return pos;
346 }
347 
ModifiedAt(int pos)348 void Document::ModifiedAt(int pos) {
349 	if (endStyled > pos)
350 		endStyled = pos;
351 }
352 
CheckReadOnly()353 void Document::CheckReadOnly() {
354 	if (cb.IsReadOnly() && enteredReadOnlyCount == 0) {
355 		enteredReadOnlyCount++;
356 		NotifyModifyAttempt();
357 		enteredReadOnlyCount--;
358 	}
359 }
360 
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
DeleteChars(int pos,int len)365 bool Document::DeleteChars(int pos, int len) {
366 	if (len == 0)
367 		return false;
368 	if ((pos + len) > Length())
369 		return false;
370 	CheckReadOnly();
371 	if (enteredCount != 0) {
372 		return false;
373 	} else {
374 		enteredCount++;
375 		if (!cb.IsReadOnly()) {
376 			NotifyModified(
377 			    DocModification(
378 			        SC_MOD_BEFOREDELETE | SC_PERFORMED_USER,
379 			        pos, len,
380 			        0, 0));
381 			int prevLinesTotal = LinesTotal();
382 			bool startSavePoint = cb.IsSavePoint();
383 			const char *text = cb.DeleteChars(pos * 2, len * 2);
384 			if (startSavePoint && cb.IsCollectingUndo())
385 				NotifySavePoint(!startSavePoint);
386 			if ((pos < Length()) || (pos == 0))
387 				ModifiedAt(pos);
388 			else
389 				ModifiedAt(pos-1);
390 			NotifyModified(
391 			    DocModification(
392 			        SC_MOD_DELETETEXT | SC_PERFORMED_USER,
393 			        pos, len,
394 			        LinesTotal() - prevLinesTotal, text));
395 		}
396 		enteredCount--;
397 	}
398 	return !cb.IsReadOnly();
399 }
400 
401 /**
402  * Insert a styled string (char/style pairs) with a length.
403  */
InsertStyledString(int position,char * s,int insertLength)404 bool Document::InsertStyledString(int position, char *s, int insertLength) {
405 	CheckReadOnly();
406 	if (enteredCount != 0) {
407 		return false;
408 	} else {
409 		enteredCount++;
410 		if (!cb.IsReadOnly()) {
411 			NotifyModified(
412 			    DocModification(
413 			        SC_MOD_BEFOREINSERT | SC_PERFORMED_USER,
414 			        position / 2, insertLength / 2,
415 			        0, s));
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);
422 			NotifyModified(
423 			    DocModification(
424 			        SC_MOD_INSERTTEXT | SC_PERFORMED_USER,
425 			        position / 2, insertLength / 2,
426 			        LinesTotal() - prevLinesTotal, text));
427 		}
428 		enteredCount--;
429 	}
430 	return !cb.IsReadOnly();
431 }
432 
Undo()433 int Document::Undo() {
434 	int newPos = -1;
435 	CheckReadOnly();
436 	if (enteredCount == 0) {
437 		enteredCount++;
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));
478 			}
479 
480 			bool endSavePoint = cb.IsSavePoint();
481 			if (startSavePoint != endSavePoint)
482 				NotifySavePoint(endSavePoint);
483 		}
484 		enteredCount--;
485 	}
486 	return newPos;
487 }
488 
Redo()489 int Document::Redo() {
490 	int newPos = -1;
491 	CheckReadOnly();
492 	if (enteredCount == 0) {
493 		enteredCount++;
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));
532 			}
533 
534 			bool endSavePoint = cb.IsSavePoint();
535 			if (startSavePoint != endSavePoint)
536 				NotifySavePoint(endSavePoint);
537 		}
538 		enteredCount--;
539 	}
540 	return newPos;
541 }
542 
543 /**
544  * Insert a single character.
545  */
InsertChar(int pos,char ch)546 bool Document::InsertChar(int pos, char ch) {
547 	char chs[2];
548 	chs[0] = ch;
549 	chs[1] = 0;
550 	return InsertStyledString(pos*2, chs, 2);
551 }
552 
553 /**
554  * Insert a null terminated string.
555  */
InsertString(int position,const char * s)556 bool Document::InsertString(int position, const char *s) {
557 	return InsertString(position, s, strlen(s));
558 }
559 
560 /**
561  * Insert a string with a length.
562  */
InsertString(int position,const char * s,size_t insertLength)563 bool Document::InsertString(int position, const char *s, size_t insertLength) {
564 	bool changed = false;
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;
575 		}
576 	}
577 	return changed;
578 }
579 
ChangeChar(int pos,char ch)580 void Document::ChangeChar(int pos, char ch) {
581 	DeleteChars(pos, 1);
582 	InsertChar(pos, ch);
583 }
584 
DelChar(int pos)585 void Document::DelChar(int pos) {
586 	DeleteChars(pos, LenChar(pos));
587 }
588 
DelCharBack(int pos)589 void Document::DelCharBack(int pos) {
590 	if (pos <= 0) {
591 		return;
592 	} else if (IsCrLf(pos - 2)) {
593 		DeleteChars(pos - 2, 2);
594 	} else if (dbcsCodePage) {
595 		int startChar = MovePositionOutsideChar(pos - 1, -1, false);
596 		DeleteChars(startChar, pos - startChar);
597 	} else {
598 		DeleteChars(pos - 1, 1);
599 	}
600 }
601 
isindentchar(char ch)602 static bool isindentchar(char ch) {
603 	return (ch == ' ') || (ch == '\t');
604 }
605 
NextTab(int pos,int tabSize)606 static int NextTab(int pos, int tabSize) {
607 	return ((pos / tabSize) + 1) * tabSize;
608 }
609 
CreateIndentation(char * linebuf,int length,int indent,int tabSize,bool insertSpaces)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--;
617 		}
618 	}
619 	while ((indent > 0) && (length > 0)) {
620 		*linebuf++ = ' ';
621 		indent--;
622 		length--;
623 	}
624 	*linebuf = '\0';
625 }
626 
GetLineIndentation(int line)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();
632 		for (int i = lineStart;i < length;i++) {
633 			char ch = cb.CharAt(i);
634 			if (ch == ' ')
635 				indent++;
636 			else if (ch == '\t')
637 				indent = NextTab(indent, tabInChars);
638 			else
639 				return indent;
640 		}
641 	}
642 	return indent;
643 }
644 
SetLineIndentation(int line,int indent)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);
654 		BeginUndoAction();
655 		DeleteChars(thisLineStart, indentPos - thisLineStart);
656 		InsertString(thisLineStart, linebuf);
657 		EndUndoAction();
658 	}
659 }
660 
GetLineIndentPosition(int line)661 int Document::GetLineIndentPosition(int line) {
662 	if (line < 0)
663 		return 0;
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 
GetColumn(int pos)672 int Document::GetColumn(int pos) {
673 	int column = 0;
674 	int line = LineFromPosition(pos);
675 	if ((line >= 0) && (line < LinesTotal())) {
676 		for (int i = LineStart(line);i < pos;) {
677 			char ch = cb.CharAt(i);
678 			if (ch == '\t') {
679 				column = NextTab(column, tabInChars);
680 				i++;
681 			} else if (ch == '\r') {
682 				return column;
683 			} else if (ch == '\n') {
684 				return column;
685 			} else {
686 				column++;
687 				i = MovePositionOutsideChar(i + 1, 1);
688 			}
689 		}
690 	}
691 	return column;
692 }
693 
FindColumn(int line,int column)694 int Document::FindColumn(int line, int column) {
695 	int position = LineStart(line);
696 	int columnCurrent = 0;
697 	if ((line >= 0) && (line < LinesTotal())) {
698 		while ((columnCurrent < column) && (position < Length())) {
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 
Indent(bool forwards,int lineBottom,int lineTop)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);
720 		if (forwards) {
721 			if (LineStart(line) < LineEnd(line)) {
722 				SetLineIndentation(line, indentOfLine + IndentSize());
723 			}
724 		} else {
725 			SetLineIndentation(line, indentOfLine - IndentSize());
726 		}
727 	}
728 }
729 
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.
TransformLineEnds(int * pLenOut,const char * s,size_t len,int eolMode)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 
ConvertLineEnds(int eolModeSet)761 void Document::ConvertLineEnds(int eolModeSet) {
762 	BeginUndoAction();
763 
764 	for (int pos = 0; pos < Length(); pos++) {
765 		if (cb.CharAt(pos) == '\r') {
766 			if (cb.CharAt(pos + 1) == '\n') {
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
772 				} else {
773 					pos++;
774 				}
775 			} else {
776 				// CR
777 				if (eolModeSet == SC_EOL_CRLF) {
778 					InsertString(pos + 1, "\n", 1); // Insert LF
779 					pos++;
780 				} else if (eolModeSet == SC_EOL_LF) {
781 					InsertString(pos, "\n", 1); // Insert LF
782 					DeleteChars(pos + 1, 1); // Delete CR
783 				}
784 			}
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 			}
794 		}
795 	}
796 
797 	EndUndoAction();
798 }
799 
IsWhiteLine(int line)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;
808 	}
809 	return true;
810 }
811 
ParaUp(int pos)812 int Document::ParaUp(int pos) {
813 	int line = LineFromPosition(pos);
814 	line--;
815 	while (line >= 0 && IsWhiteLine(line)) { // skip empty lines
816 		line--;
817 	}
818 	while (line >= 0 && !IsWhiteLine(line)) { // skip non-empty lines
819 		line--;
820 	}
821 	line++;
822 	return LineStart(line);
823 }
824 
ParaDown(int pos)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 
WordCharClass(unsigned char ch)839 CharClassify::cc Document::WordCharClass(unsigned char ch) {
840 	if ((SC_CP_UTF8 == dbcsCodePage) && (ch >= 0x80))
841 		return CharClassify::ccWord;
842 	return charClass.GetClass(ch);
843 }
844 
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  */
ExtendWordSelect(int pos,int delta,bool onlyWordCharacters)849 int Document::ExtendWordSelect(int pos, int delta, bool onlyWordCharacters) {
850 	CharClassify::cc ccStart = CharClassify::ccWord;
851 	if (delta < 0) {
852 		if (!onlyWordCharacters)
853 			ccStart = WordCharClass(cb.CharAt(pos-1));
854 		while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart))
855 			pos--;
856 	} else {
857 		if (!onlyWordCharacters)
858 			ccStart = WordCharClass(cb.CharAt(pos));
859 		while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
860 			pos++;
861 	}
862 	return MovePositionOutsideChar(pos, delta);
863 }
864 
865 /**
866  * Find the start of the next word in either a forward (delta >= 0) or backwards direction
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  */
NextWordStart(int pos,int delta)872 int Document::NextWordStart(int pos, int delta) {
873 	if (delta < 0) {
874 		while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace))
875 			pos--;
876 		if (pos > 0) {
877 			CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));
878 			while (pos > 0 && (WordCharClass(cb.CharAt(pos - 1)) == ccStart)) {
879 				pos--;
880 			}
881 		}
882 	} else {
883 		CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));
884 		while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == ccStart))
885 			pos++;
886 		while (pos < (Length()) && (WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace))
887 			pos++;
888 	}
889 	return pos;
890 }
891 
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  */
NextWordEnd(int pos,int delta)899 int Document::NextWordEnd(int pos, int delta) {
900 	if (delta < 0) {
901 		if (pos > 0) {
902 			CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos-1));
903 			if (ccStart != CharClassify::ccSpace) {
904 				while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == ccStart) {
905 					pos--;
906 				}
907 			}
908 			while (pos > 0 && WordCharClass(cb.CharAt(pos - 1)) == CharClassify::ccSpace) {
909 				pos--;
910 			}
911 		}
912 	} else {
913 		while (pos < Length() && WordCharClass(cb.CharAt(pos)) == CharClassify::ccSpace) {
914 			pos++;
915 		}
916 		if (pos < Length()) {
917 			CharClassify::cc ccStart = WordCharClass(cb.CharAt(pos));
918 			while (pos < Length() && WordCharClass(cb.CharAt(pos)) == ccStart) {
919 				pos++;
920 			}
921 		}
922 	}
923 	return pos;
924 }
925 
926 /**
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.
929  */
IsWordStartAt(int pos)930 bool Document::IsWordStartAt(int pos) {
931 	if (pos > 0) {
932 		CharClassify::cc ccPos = WordCharClass(CharAt(pos));
933 		return (ccPos == CharClassify::ccWord || ccPos == CharClassify::ccPunctuation) &&
934 			(ccPos != WordCharClass(CharAt(pos - 1)));
935 	}
936 	return true;
937 }
938 
939 /**
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.
942  */
IsWordEndAt(int pos)943 bool Document::IsWordEndAt(int pos) {
944 	if (pos < Length()) {
945 		CharClassify::cc ccPrev = WordCharClass(CharAt(pos-1));
946 		return (ccPrev == CharClassify::ccWord || ccPrev == CharClassify::ccPunctuation) &&
947 			(ccPrev != WordCharClass(CharAt(pos)));
948 	}
949 	return true;
950 }
951 
952 /**
953  * Check that the given range is has transitions between character classes at both
954  * ends and where the characters on the inside are word or punctuation characters.
955  */
IsWordAt(int start,int end)956 bool Document::IsWordAt(int start, int end) {
957 	return IsWordStartAt(start) && IsWordEndAt(end);
958 }
959 
960 // The comparison and case changing functions here assume ASCII
961 // or extended ASCII such as the normal Windows code page.
962 
MakeUpperCase(char ch)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 
MakeLowerCase(char ch)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:
DocumentIndexer(Document * pdoc_,int end_)982 	DocumentIndexer(Document *pdoc_, int end_) :
983 		pdoc(pdoc_), end(end_) {
984 	}
985 
~DocumentIndexer()986 	virtual ~DocumentIndexer() {
987 	}
988 
CharAt(int index)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  */
FindText(int minPos,int maxPos,const char * s,bool caseSensitive,bool word,bool wordStart,bool regExp,bool posix,int * length)1002 long Document::FindText(int minPos, int maxPos, const char *s,
1003                         bool caseSensitive, bool word, bool wordStart, bool regExp, bool posix,
1004                         int *length) {
1005 	if (regExp) {
1006 		if (!pre)
1007 			pre = new RESearch(&charClass);
1008 		if (!pre)
1009 			return -1;
1010 
1011 		int increment = (minPos <= maxPos) ? 1 : -1;
1012 
1013 		int startPos = minPos;
1014 		int endPos = maxPos;
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 
1020 		const char *errmsg = pre->Compile(s, *length, caseSensitive, posix);
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);
1030 		if ((increment == 1) &&
1031 			(startPos >= LineEnd(lineRangeStart)) &&
1032 			(lineRangeStart < lineRangeEnd)) {
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];
1040 		int lineRangeBreak = lineRangeEnd + increment;
1041 		for (int line = lineRangeStart; line != lineRangeBreak; line += increment) {
1042 			int startOfLine = LineStart(line);
1043 			int endOfLine = LineEnd(line);
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
1064 					endOfLine = startPos;
1065 				}
1066 			}
1067 
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];
1073 				if (increment == -1) {
1074 					// Check for the last match on this line.
1075 					int repetitions = 1000;	// Break out of infinite loop
1076 					while (success && (pre->eopat[0] <= endOfLine) && (repetitions--)) {
1077 						success = pre->Execute(di, pos+1, endOfLine);
1078 						if (success) {
1079 							if (pre->eopat[0] <= minPos) {
1080 								pos = pre->bopat[0];
1081 								lenRet = pre->eopat[0] - pre->bopat[0];
1082 							} else {
1083 								success = 0;
1084 							}
1085 						}
1086 					}
1087 				}
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)
1106 			lengthFind = static_cast<int>(strlen(s));
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));
1115 		int pos = forward ? startPos : (startPos - 1);
1116 		while (forward ? (pos < endSearch) : (pos >= endSearch)) {
1117 			char ch = CharAt(pos);
1118 			if (caseSensitive) {
1119 				if (ch == firstChar) {
1120 					bool found = true;
1121 					if (pos + lengthFind > Platform::Maximum(startPos, endPos)) found = false;
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 					}
1133 				}
1134 			} else {
1135 				if (MakeUpperCase(ch) == firstChar) {
1136 					bool found = true;
1137 					if (pos + lengthFind > Platform::Maximum(startPos, endPos)) found = false;
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 					}
1149 				}
1150 			}
1151 			pos += increment;
1152 			if (dbcsCodePage && (pos >= 0)) {
1153 				// Ensure trying to match from start of character
1154 				pos = MovePositionOutsideChar(pos, increment, false);
1155 			}
1156 		}
1157 	}
1158 	//Platform::DebugPrintf("Not found\n");
1159 	return -1;
1160 }
1161 
SubstituteByPosition(const char * text,int * length)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++) {
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 			}
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++) {
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 			}
1236 		} else {
1237 			*o++ = text[j];
1238 		}
1239 	}
1240 	*o = '\0';
1241 	*length = lenResult;
1242 	return substituted;
1243 }
1244 
LinesTotal()1245 int Document::LinesTotal() {
1246 	return cb.Lines();
1247 }
1248 
ChangeCase(Range r,bool makeUpperCase)1249 void Document::ChangeCase(Range r, bool makeUpperCase) {
1250 	for (int pos = r.start; pos < r.end;) {
1251 		int len = LenChar(pos);
1252 		if (len == 1) {
1253 			char ch = CharAt(pos);
1254 			if (makeUpperCase) {
1255 				if (IsLowerCase(ch)) {
1256 					ChangeChar(pos, static_cast<char>(MakeUpperCase(ch)));
1257 				}
1258 			} else {
1259 				if (IsUpperCase(ch)) {
1260 					ChangeChar(pos, static_cast<char>(MakeLowerCase(ch)));
1261 				}
1262 			}
1263 		}
1264 		pos += len;
1265 	}
1266 }
1267 
SetDefaultCharClasses(bool includeWordClass)1268 void Document::SetDefaultCharClasses(bool includeWordClass) {
1269     charClass.SetDefaultCharClasses(includeWordClass);
1270 }
1271 
SetCharClasses(const unsigned char * chars,CharClassify::cc newCharClass)1272 void Document::SetCharClasses(const unsigned char *chars, CharClassify::cc newCharClass) {
1273     charClass.SetCharClasses(chars, newCharClass);
1274 }
1275 
SetStylingBits(int bits)1276 void Document::SetStylingBits(int bits) {
1277 	stylingBits = bits;
1278 	stylingBitsMask = 0;
1279 	for (int bit = 0; bit < stylingBits; bit++) {
1280 		stylingBitsMask <<= 1;
1281 		stylingBitsMask |= 1;
1282 	}
1283 }
1284 
StartStyling(int position,char mask)1285 void Document::StartStyling(int position, char mask) {
1286 	stylingMask = mask;
1287 	endStyled = position;
1288 }
1289 
SetStyleFor(int length,char style)1290 bool Document::SetStyleFor(int length, char style) {
1291 	if (enteredCount != 0) {
1292 		return false;
1293 	} else {
1294 		enteredCount++;
1295 		style &= stylingMask;
1296 		int prevEndStyled = endStyled;
1297 		if (cb.SetStyleFor(endStyled, length, style, stylingMask)) {
1298 			DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1299 			                   prevEndStyled, length);
1300 			NotifyModified(mh);
1301 		}
1302 		endStyled += length;
1303 		enteredCount--;
1304 		return true;
1305 	}
1306 }
1307 
SetStyles(int length,char * styles)1308 bool Document::SetStyles(int length, char *styles) {
1309 	if (enteredCount != 0) {
1310 		return false;
1311 	} else {
1312 		enteredCount++;
1313 		bool didChange = false;
1314 		int startMod = 0;
1315 		int endMod = 0;
1316 		for (int iPos = 0; iPos < length; iPos++, endStyled++) {
1317 			PLATFORM_ASSERT(endStyled < Length());
1318 			if (cb.SetStyleAt(endStyled, styles[iPos], stylingMask)) {
1319 				if (!didChange) {
1320 					startMod = endStyled;
1321 				}
1322 				didChange = true;
1323 				endMod = endStyled;
1324 			}
1325 		}
1326 		if (didChange) {
1327 			DocModification mh(SC_MOD_CHANGESTYLE | SC_PERFORMED_USER,
1328 			                   startMod, endMod - startMod + 1);
1329 			NotifyModified(mh);
1330 		}
1331 		enteredCount--;
1332 		return true;
1333 	}
1334 }
1335 
EnsureStyledTo(int pos)1336 bool Document::EnsureStyledTo(int pos) {
1337 	if (pos > GetEndStyled()) {
1338 		IncrementStyleClock();
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 		}
1343 	}
1344 	return pos <= GetEndStyled();
1345 }
1346 
IncrementStyleClock()1347 void Document::IncrementStyleClock() {
1348 	styleClock++;
1349 	if (styleClock > 0x100000) {
1350 		styleClock = 0;
1351 	}
1352 }
1353 
AddWatcher(DocWatcher * watcher,void * userData)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 
RemoveWatcher(DocWatcher * watcher,void * userData)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 
NotifyModifyAttempt()1398 void Document::NotifyModifyAttempt() {
1399 	for (int i = 0; i < lenWatchers; i++) {
1400 		watchers[i].watcher->NotifyModifyAttempt(this, watchers[i].userData);
1401 	}
1402 }
1403 
NotifySavePoint(bool atSavePoint)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 
NotifyModified(DocModification mh)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 }
1415 
IsWordPartSeparator(char ch)1416 bool Document::IsWordPartSeparator(char ch) {
1417 	return (WordCharClass(ch) == CharClassify::ccWord) && IsPunctuation(ch);
1418 }
1419 
WordPartLeft(int pos)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;
1432 			if (IsLowerCase(startChar)) {
1433 				while (pos > 0 && IsLowerCase(cb.CharAt(pos)))
1434 					--pos;
1435 				if (!IsUpperCase(cb.CharAt(pos)) && !IsLowerCase(cb.CharAt(pos)))
1436 					++pos;
1437 			} else if (IsUpperCase(startChar)) {
1438 				while (pos > 0 && IsUpperCase(cb.CharAt(pos)))
1439 					--pos;
1440 				if (!IsUpperCase(cb.CharAt(pos)))
1441 					++pos;
1442 			} else if (IsADigit(startChar)) {
1443 				while (pos > 0 && IsADigit(cb.CharAt(pos)))
1444 					--pos;
1445 				if (!IsADigit(cb.CharAt(pos)))
1446 					++pos;
1447 			} else if (IsPunctuation(startChar)) {
1448 				while (pos > 0 && IsPunctuation(cb.CharAt(pos)))
1449 					--pos;
1450 				if (!IsPunctuation(cb.CharAt(pos)))
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;
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;
1464 			}
1465 		}
1466 	}
1467 	return pos;
1468 }
1469 
WordPartRight(int pos)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 	}
1478 	if (!isascii(startChar)) {
1479 		while (pos < length && !isascii(cb.CharAt(pos)))
1480 			++pos;
1481 	} else if (IsLowerCase(startChar)) {
1482 		while (pos < length && IsLowerCase(cb.CharAt(pos)))
1483 			++pos;
1484 	} else if (IsUpperCase(startChar)) {
1485 		if (IsLowerCase(cb.CharAt(pos + 1))) {
1486 			++pos;
1487 			while (pos < length && IsLowerCase(cb.CharAt(pos)))
1488 				++pos;
1489 		} else {
1490 			while (pos < length && IsUpperCase(cb.CharAt(pos)))
1491 				++pos;
1492 		}
1493 		if (IsLowerCase(cb.CharAt(pos)) && IsUpperCase(cb.CharAt(pos - 1)))
1494 			--pos;
1495 	} else if (IsADigit(startChar)) {
1496 		while (pos < length && IsADigit(cb.CharAt(pos)))
1497 			++pos;
1498 	} else if (IsPunctuation(startChar)) {
1499 		while (pos < length && IsPunctuation(cb.CharAt(pos)))
1500 			++pos;
1501 	} else if (isspacechar(startChar)) {
1502 		while (pos < length && isspacechar(cb.CharAt(pos)))
1503 			++pos;
1504 	} else {
1505 		++pos;
1506 	}
1507 	return pos;
1508 }
1509 
IsLineEndChar(char c)1510 bool IsLineEndChar(char c) {
1511 	return (c == '\n' || c == '\r');
1512 }
1513 
ExtendStyleRange(int pos,int delta,bool singleLine)1514 int Document::ExtendStyleRange(int pos, int delta, bool singleLine) {
1515 	int sStart = cb.StyleAt(pos);
1516 	if (delta < 0) {
1517 		while (pos > 0 && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )
1518 			pos--;
1519 		pos++;
1520 	} else {
1521 		while (pos < (Length()) && (cb.StyleAt(pos) == sStart) && (!singleLine || !IsLineEndChar(cb.CharAt(pos))) )
1522 			pos++;
1523 	}
1524 	return pos;
1525 }
1526 
BraceOpposite(char ch)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
BraceMatch(int position,int)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 }
1578