]>
git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/Document.cxx
95107eb0874f7f38c5ad8fd2be1acf6cc8bd1d58
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.
13 #include "Scintilla.h"
15 #include "CellBuffer.h"
18 Document::Document() {
23 eolMode
= SC_EOL_CRLF
;
27 stylingBitsMask
= 0x1F;
30 for (int ch
= 0; ch
< 256; ch
++) {
31 wordchars
[ch
] = isalnum(ch
) || ch
== '_';
40 Document::~Document() {
41 for (int i
= 0; i
< lenWatchers
; i
++) {
42 watchers
[i
].watcher
->NotifyDeleted(this, watchers
[i
].userData
);
49 // Increase reference count and return its previous value.
50 int Document::AddRef() {
54 // Decrease reference count and return its provius value.
55 // Delete the document if reference count reaches zero.
56 int Document::Release() {
57 int curRefCount
= --refCount
;
63 void Document::SetSavePoint() {
65 NotifySavePoint(true);
68 int Document::AddMark(int line
, int markerNum
) {
69 int prev
= cb
.AddMark(line
, markerNum
);
70 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
75 void Document::DeleteMark(int line
, int markerNum
) {
76 cb
.DeleteMark(line
, markerNum
);
77 DocModification
mh(SC_MOD_CHANGEMARKER
, LineStart(line
), 0, 0, 0);
81 void Document::DeleteMarkFromHandle(int markerHandle
) {
82 cb
.DeleteMarkFromHandle(markerHandle
);
83 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
87 void Document::DeleteAllMarks(int markerNum
) {
88 cb
.DeleteAllMarks(markerNum
);
89 DocModification
mh(SC_MOD_CHANGEMARKER
, 0, 0, 0, 0);
93 int Document::LineStart(int line
) {
94 return cb
.LineStart(line
);
97 int Document::LineEnd(int line
) {
98 if (line
== LinesTotal() - 1) {
99 return LineStart(line
+ 1);
101 int position
= LineStart(line
+ 1) - 1;
102 // When line terminator is CR+LF, may need to go back one more
103 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
110 int Document::LineFromPosition(int pos
) {
111 return cb
.LineFromPosition(pos
);
114 int Document::LineEndPosition(int position
) {
115 return LineEnd(LineFromPosition(position
));
118 int Document::VCHomePosition(int position
) {
119 int line
= LineFromPosition(position
);
120 int startPosition
= LineStart(line
);
121 int endLine
= LineStart(line
+ 1) - 1;
122 int startText
= startPosition
;
123 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t' ) )
125 if (position
== startText
)
126 return startPosition
;
131 int Document::SetLevel(int line
, int level
) {
132 int prev
= cb
.SetLevel(line
, level
);
134 DocModification
mh(SC_MOD_CHANGEFOLD
, LineStart(line
), 0, 0, 0);
136 mh
.foldLevelNow
= level
;
137 mh
.foldLevelPrev
= prev
;
143 static bool IsSubordinate(int levelStart
, int levelTry
) {
144 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
147 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
150 int Document::GetLastChild(int lineParent
, int level
) {
152 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
153 int maxLine
= LinesTotal();
154 int lineMaxSubord
= lineParent
;
155 while (lineMaxSubord
< maxLine
-1) {
156 EnsureStyledTo(LineStart(lineMaxSubord
+2));
157 if (!IsSubordinate(level
, GetLevel(lineMaxSubord
+1)))
161 if (lineMaxSubord
> lineParent
) {
162 if (level
> (GetLevel(lineMaxSubord
+1) & SC_FOLDLEVELNUMBERMASK
)) {
163 // Have chewed up some whitespace that belongs to a parent so seek back
164 if ((lineMaxSubord
> lineParent
) && (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
)) {
169 return lineMaxSubord
;
172 int Document::GetFoldParent(int line
) {
173 int level
= GetLevel(line
);
174 int lineLook
= line
-1;
175 while ((lineLook
> 0) && (
176 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
177 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
181 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
182 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
189 int Document::ClampPositionIntoDocument(int pos
) {
190 return Platform::Clamp(pos
, 0, Length());
193 bool Document::IsCrLf(int pos
) {
196 if (pos
>= (Length() - 1))
198 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
201 bool Document::IsDBCS(int pos
) {
204 // Anchor DBCS calculations at start of line because start of line can
205 // not be a DBCS trail byte.
207 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
209 while (startLine
<= pos
) {
210 if (IsDBCSLeadByteEx(dbcsCodePage
, cb
.CharAt(startLine
))) {
212 if (startLine
>= pos
)
224 // Normalise a position so that it is not halfway through a two byte character.
225 // This can occur in two situations -
226 // When lines are terminated with \r\n pairs which should be treated as one character.
227 // When displaying DBCS text such as Japanese.
228 // If moving, move the position in the indicated direction.
229 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
230 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
231 // If out of range, just return value - should be fixed up after
237 // Position 0 and Length() can not be between any two characters
243 // assert pos > 0 && pos < Length()
244 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
251 // Not between CR and LF
255 // Anchor DBCS calculations at start of line because start of line can
256 // not be a DBCS trail byte.
258 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
260 bool atLeadByte
= false;
261 while (startLine
< pos
) {
264 else if (IsDBCSLeadByteEx(dbcsCodePage
, cb
.CharAt(startLine
)))
269 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
273 // Position is between a lead byte and a trail byte
285 void Document::ModifiedAt(int pos
) {
290 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
291 // SetStyleAt does not change the persistent state of a document
293 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
294 void Document::DeleteChars(int pos
, int len
) {
295 if (enteredCount
== 0) {
298 NotifyModifyAttempt();
299 if (!cb
.IsReadOnly()) {
300 int prevLinesTotal
= LinesTotal();
301 bool startSavePoint
= cb
.IsSavePoint();
302 const char *text
= cb
.DeleteChars(pos
*2, len
* 2);
303 if (startSavePoint
&& cb
.IsCollectingUndo())
304 NotifySavePoint(!startSavePoint
);
306 int modFlags
= SC_MOD_DELETETEXT
| SC_PERFORMED_USER
;
307 DocModification
mh(modFlags
, pos
, len
, LinesTotal() - prevLinesTotal
, text
);
314 void Document::InsertStyledString(int position
, char *s
, int insertLength
) {
315 if (enteredCount
== 0) {
318 NotifyModifyAttempt();
319 if (!cb
.IsReadOnly()) {
320 int prevLinesTotal
= LinesTotal();
321 bool startSavePoint
= cb
.IsSavePoint();
322 const char *text
= cb
.InsertString(position
, s
, insertLength
);
323 if (startSavePoint
&& cb
.IsCollectingUndo())
324 NotifySavePoint(!startSavePoint
);
325 ModifiedAt(position
/ 2);
327 int modFlags
= SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
;
328 DocModification
mh(modFlags
, position
/ 2, insertLength
/ 2, LinesTotal() - prevLinesTotal
, text
);
335 int Document::Undo() {
337 if (enteredCount
== 0) {
339 bool startSavePoint
= cb
.IsSavePoint();
340 int steps
= cb
.StartUndo();
341 //Platform::DebugPrintf("Steps=%d\n", steps);
342 for (int step
=0; step
<steps
; step
++) {
343 int prevLinesTotal
= LinesTotal();
344 const Action
&action
= cb
.UndoStep();
345 int cellPosition
= action
.position
/ 2;
346 ModifiedAt(cellPosition
);
347 newPos
= cellPosition
;
349 int modFlags
= SC_PERFORMED_UNDO
;
350 // With undo, an insertion action becomes a deletion notification
351 if (action
.at
== removeAction
) {
352 newPos
+= action
.lenData
;
353 modFlags
|= SC_MOD_INSERTTEXT
;
355 modFlags
|= SC_MOD_DELETETEXT
;
358 modFlags
|= SC_LASTSTEPINUNDOREDO
;
359 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
360 LinesTotal() - prevLinesTotal
, action
.data
));
363 bool endSavePoint
= cb
.IsSavePoint();
364 if (startSavePoint
!= endSavePoint
)
365 NotifySavePoint(endSavePoint
);
371 int Document::Redo() {
373 if (enteredCount
== 0) {
375 bool startSavePoint
= cb
.IsSavePoint();
376 int steps
= cb
.StartRedo();
377 for (int step
=0; step
<steps
; step
++) {
378 int prevLinesTotal
= LinesTotal();
379 const Action
&action
= cb
.RedoStep();
380 int cellPosition
= action
.position
/ 2;
381 ModifiedAt(cellPosition
);
382 newPos
= cellPosition
;
384 int modFlags
= SC_PERFORMED_REDO
;
385 if (action
.at
== insertAction
) {
386 newPos
+= action
.lenData
;
387 modFlags
|= SC_MOD_INSERTTEXT
;
389 modFlags
|= SC_MOD_DELETETEXT
;
392 modFlags
|= SC_LASTSTEPINUNDOREDO
;
393 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
394 LinesTotal() - prevLinesTotal
, action
.data
));
397 bool endSavePoint
= cb
.IsSavePoint();
398 if (startSavePoint
!= endSavePoint
)
399 NotifySavePoint(endSavePoint
);
405 void Document::InsertChar(int pos
, char ch
) {
409 InsertStyledString(pos
*2, chs
, 2);
412 // Insert a null terminated string
413 void Document::InsertString(int position
, const char *s
) {
414 InsertString(position
, s
, strlen(s
));
417 // Insert a string with a length
418 void Document::InsertString(int position
, const char *s
, int insertLength
) {
419 char *sWithStyle
= new char[insertLength
* 2];
421 for (int i
= 0; i
< insertLength
; i
++) {
422 sWithStyle
[i
*2] = s
[i
];
423 sWithStyle
[i
*2 + 1] = 0;
425 InsertStyledString(position
*2, sWithStyle
, insertLength
*2);
430 void Document::ChangeChar(int pos
, char ch
) {
435 void Document::DelChar(int pos
) {
438 } else if (IsDBCS(pos
)) {
440 } else if (pos
< Length()) {
445 int Document::DelCharBack(int pos
) {
448 } else if (IsCrLf(pos
- 2)) {
449 DeleteChars(pos
- 2, 2);
451 } else if (IsDBCS(pos
- 1)) {
452 DeleteChars(pos
- 2, 2);
455 DeleteChars(pos
- 1, 1);
460 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
463 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
464 InsertChar(LineStart(line
), '\t');
467 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
468 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
470 while (ispc
< tabInChars
&& cb
.CharAt(LineStart(line
) + ispc
) == ' ')
472 int posStartLine
= LineStart(line
);
473 if (ispc
== tabInChars
) {
474 DeleteChars(posStartLine
, ispc
);
475 } else if (cb
.CharAt(posStartLine
+ ispc
) == '\t') {
476 DeleteChars(posStartLine
, ispc
+ 1);
477 } else { // Hit a non-white
478 DeleteChars(posStartLine
, ispc
);
484 void Document::ConvertLineEnds(int eolModeSet
) {
486 for (int pos
= 0; pos
< Length(); pos
++) {
487 if (cb
.CharAt(pos
) == '\r') {
488 if (cb
.CharAt(pos
+1) == '\n') {
489 if (eolModeSet
!= SC_EOL_CRLF
) {
491 if (eolModeSet
== SC_EOL_CR
)
492 InsertString(pos
, "\r", 1);
494 InsertString(pos
, "\n", 1);
499 if (eolModeSet
!= SC_EOL_CR
) {
501 if (eolModeSet
== SC_EOL_CRLF
) {
502 InsertString(pos
, "\r\n", 2);
505 InsertString(pos
, "\n", 1);
509 } else if (cb
.CharAt(pos
) == '\n') {
510 if (eolModeSet
!= SC_EOL_LF
) {
512 if (eolModeSet
== SC_EOL_CRLF
) {
513 InsertString(pos
, "\r\n", 2);
516 InsertString(pos
, "\r", 1);
524 bool Document::IsWordChar(unsigned char ch
) {
525 return wordchars
[ch
];
528 int Document::ExtendWordSelect(int pos
, int delta
) {
530 while (pos
> 0 && IsWordChar(cb
.CharAt(pos
- 1)))
533 while (pos
< (Length()) && IsWordChar(cb
.CharAt(pos
)))
539 int Document::NextWordStart(int pos
, int delta
) {
541 while (pos
> 0 && (cb
.CharAt(pos
- 1) == ' ' || cb
.CharAt(pos
- 1) == '\t'))
543 if (isspace(cb
.CharAt(pos
- 1))) { // Back up to previous line
544 while (pos
> 0 && isspace(cb
.CharAt(pos
- 1)))
547 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
- 1));
548 while (pos
> 0 && !isspace(cb
.CharAt(pos
- 1)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
- 1))))
552 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
));
553 while (pos
< (Length()) && isspace(cb
.CharAt(pos
)))
555 while (pos
< (Length()) && !isspace(cb
.CharAt(pos
)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
))))
557 while (pos
< (Length()) && (cb
.CharAt(pos
) == ' ' || cb
.CharAt(pos
) == '\t'))
563 bool Document::IsWordAt(int start
, int end
) {
564 int lengthDoc
= Length();
566 char ch
= CharAt(start
- 1);
570 if (end
< lengthDoc
- 1) {
571 char ch
= CharAt(end
);
578 // Find text in document, supporting both forward and backward
579 // searches (just pass minPos > maxPos to do a backward search)
580 // Has not been tested with backwards DBCS searches yet.
581 long Document::FindText(int minPos
, int maxPos
, const char *s
, bool caseSensitive
, bool word
) {
582 bool forward
= minPos
<= maxPos
;
583 int increment
= forward
? 1 : -1;
585 // Range endpoints should not be inside DBCS characters, but just in case, move them.
586 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
587 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
589 // Compute actual search ranges needed
590 int lengthFind
= strlen(s
);
592 if (startPos
<= endPos
) {
593 endSearch
= endPos
- lengthFind
+ 1;
597 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
598 char firstChar
= s
[0];
600 firstChar
= toupper(firstChar
);
602 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
603 char ch
= CharAt(pos
);
605 if (ch
== firstChar
) {
607 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
608 ch
= CharAt(pos
+ posMatch
);
609 if (ch
!= s
[posMatch
])
613 if ((!word
) || IsWordAt(pos
, pos
+ lengthFind
))
618 if (toupper(ch
) == firstChar
) {
620 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
621 ch
= CharAt(pos
+ posMatch
);
622 if (toupper(ch
) != toupper(s
[posMatch
]))
626 if ((!word
) || IsWordAt(pos
, pos
+ lengthFind
))
633 // Ensure trying to match from start of character
634 pos
= MovePositionOutsideChar(pos
, increment
, false);
637 //Platform::DebugPrintf("Not found\n");
641 int Document::LinesTotal() {
645 void Document::ChangeCase(Range r
, bool makeUpperCase
) {
646 for (int pos
=r
.start
; pos
<r
.end
; pos
++) {
647 char ch
= CharAt(pos
);
648 if (dbcsCodePage
&& IsDBCS(pos
)) {
653 ChangeChar(pos
, toupper(ch
));
657 ChangeChar(pos
, tolower(ch
));
664 void Document::SetWordChars(unsigned char *chars
) {
666 for (ch
= 0; ch
< 256; ch
++) {
667 wordchars
[ch
] = false;
671 wordchars
[*chars
] = true;
675 for (ch
= 0; ch
< 256; ch
++) {
676 wordchars
[ch
] = isalnum(ch
) || ch
== '_';
681 void Document::SetStylingBits(int bits
) {
684 for (int bit
=0; bit
<stylingBits
; bit
++) {
685 stylingBitsMask
<<= 1;
686 stylingBitsMask
|= 1;
690 void Document::StartStyling(int position
, char mask
) {
691 stylingPos
= position
;
695 void Document::SetStyleFor(int length
, char style
) {
696 if (enteredCount
== 0) {
698 int prevEndStyled
= endStyled
;
699 if (cb
.SetStyleFor(stylingPos
, length
, style
, stylingMask
)) {
700 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
701 prevEndStyled
, length
);
704 stylingPos
+= length
;
705 endStyled
= stylingPos
;
710 void Document::SetStyles(int length
, char *styles
) {
711 if (enteredCount
== 0) {
713 int prevEndStyled
= endStyled
;
714 bool didChange
= false;
715 for (int iPos
= 0; iPos
< length
; iPos
++, stylingPos
++) {
716 if (cb
.SetStyleAt(stylingPos
, styles
[iPos
], stylingMask
)) {
720 endStyled
= stylingPos
;
722 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
723 prevEndStyled
, endStyled
- prevEndStyled
);
730 bool Document::EnsureStyledTo(int pos
) {
731 // Ask the watchers to style, and stop as soon as one responds.
732 for (int i
= 0; pos
> GetEndStyled() && i
< lenWatchers
; i
++)
733 watchers
[i
].watcher
->NotifyStyleNeeded(this, watchers
[i
].userData
, pos
);
734 return pos
<= GetEndStyled();
737 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
738 for (int i
= 0; i
< lenWatchers
; i
++) {
739 if ((watchers
[i
].watcher
== watcher
) &&
740 (watchers
[i
].userData
== userData
))
743 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
746 for (int j
= 0; j
< lenWatchers
; j
++)
747 pwNew
[j
] = watchers
[j
];
748 pwNew
[lenWatchers
].watcher
= watcher
;
749 pwNew
[lenWatchers
].userData
= userData
;
756 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
757 for (int i
= 0; i
< lenWatchers
; i
++) {
758 if ((watchers
[i
].watcher
== watcher
) &&
759 (watchers
[i
].userData
== userData
)) {
760 if (lenWatchers
== 1) {
765 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
768 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
769 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
781 void Document::NotifyModifyAttempt() {
782 for (int i
= 0; i
< lenWatchers
; i
++) {
783 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
787 void Document::NotifySavePoint(bool atSavePoint
) {
788 for (int i
= 0; i
< lenWatchers
; i
++) {
789 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
793 void Document::NotifyModified(DocModification mh
) {
794 for (int i
= 0; i
< lenWatchers
; i
++) {
795 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);