]>
git.saurik.com Git - wxWidgets.git/blob - contrib/src/stc/scintilla/src/Document.cxx
7d832241fc1a65716b3a4bc74237ce736173c705
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::LineStart(int line
) {
69 return cb
.LineStart(line
);
72 int Document::LineFromPosition(int pos
) {
73 return cb
.LineFromPosition(pos
);
76 int Document::LineEndPosition(int position
) {
77 int line
= LineFromPosition(position
);
78 if (line
== LinesTotal() - 1) {
79 position
= LineStart(line
+ 1);
81 position
= LineStart(line
+ 1) - 1;
82 // When line terminator is CR+LF, may need to go back one more
83 if ((position
> LineStart(line
)) && (cb
.CharAt(position
- 1) == '\r')) {
90 int Document::VCHomePosition(int position
) {
91 int line
= LineFromPosition(position
);
92 int startPosition
= LineStart(line
);
93 int endLine
= LineStart(line
+ 1) - 1;
94 int startText
= startPosition
;
95 while (startText
< endLine
&& (cb
.CharAt(startText
) == ' ' || cb
.CharAt(startText
) == '\t' ) )
97 if (position
== startText
)
103 int Document::SetLevel(int line
, int level
) {
104 int prev
= cb
.SetLevel(line
, level
);
106 DocModification
mh(SC_MOD_CHANGEFOLD
, LineStart(line
), 0, 0, 0);
108 mh
.foldLevelNow
= level
;
109 mh
.foldLevelPrev
= prev
;
115 static bool IsSubordinate(int levelStart
, int levelTry
) {
116 if (levelTry
& SC_FOLDLEVELWHITEFLAG
)
119 return (levelStart
& SC_FOLDLEVELNUMBERMASK
) < (levelTry
& SC_FOLDLEVELNUMBERMASK
);
122 int Document::GetLastChild(int lineParent
, int level
) {
124 level
= GetLevel(lineParent
) & SC_FOLDLEVELNUMBERMASK
;
125 int maxLine
= LinesTotal();
126 int lineMaxSubord
= lineParent
;
127 while ((lineMaxSubord
< maxLine
-1) && IsSubordinate(level
, GetLevel(lineMaxSubord
+1))) {
130 if (lineMaxSubord
> lineParent
) {
131 if (level
> (GetLevel(lineMaxSubord
+1) & SC_FOLDLEVELNUMBERMASK
)) {
132 // Have chewed up some whitespace that belongs to a parent so seek back
133 if ((lineMaxSubord
> lineParent
) && (GetLevel(lineMaxSubord
) & SC_FOLDLEVELWHITEFLAG
)) {
138 return lineMaxSubord
;
141 int Document::GetFoldParent(int line
) {
142 int level
= GetLevel(line
);
143 int lineLook
= line
-1;
144 while ((lineLook
> 0) && (
145 (!(GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
)) ||
146 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) >= level
))
150 if ((GetLevel(lineLook
) & SC_FOLDLEVELHEADERFLAG
) &&
151 ((GetLevel(lineLook
) & SC_FOLDLEVELNUMBERMASK
) < level
)) {
158 int Document::ClampPositionIntoDocument(int pos
) {
159 return Platform::Clamp(pos
, 0, Length());
162 bool Document::IsCrLf(int pos
) {
165 if (pos
>= (Length() - 1))
167 return (cb
.CharAt(pos
) == '\r') && (cb
.CharAt(pos
+ 1) == '\n');
170 bool Document::IsDBCS(int pos
) {
173 // Anchor DBCS calculations at start of line because start of line can
174 // not be a DBCS trail byte.
176 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
178 while (startLine
<= pos
) {
179 if (IsDBCSLeadByteEx(dbcsCodePage
, cb
.CharAt(startLine
))) {
181 if (startLine
>= pos
)
193 // Normalise a position so that it is not halfway through a two byte character.
194 // This can occur in two situations -
195 // When lines are terminated with \r\n pairs which should be treated as one character.
196 // When displaying DBCS text such as Japanese.
197 // If moving, move the position in the indicated direction.
198 int Document::MovePositionOutsideChar(int pos
, int moveDir
, bool checkLineEnd
) {
199 //Platform::DebugPrintf("NoCRLF %d %d\n", pos, moveDir);
200 // If out of range, just return value - should be fixed up after
206 // Position 0 and Length() can not be between any two characters
212 // assert pos > 0 && pos < Length()
213 if (checkLineEnd
&& IsCrLf(pos
- 1)) {
220 // Not between CR and LF
224 // Anchor DBCS calculations at start of line because start of line can
225 // not be a DBCS trail byte.
227 while (startLine
> 0 && cb
.CharAt(startLine
) != '\r' && cb
.CharAt(startLine
) != '\n')
229 bool atLeadByte
= false;
230 while (startLine
< pos
) {
233 else if (IsDBCSLeadByteEx(dbcsCodePage
, cb
.CharAt(startLine
)))
238 //Platform::DebugPrintf("DBCS %s\n", atlead ? "D" : "-");
242 // Position is between a lead byte and a trail byte
254 void Document::ModifiedAt(int pos
) {
259 // Document only modified by gateways DeleteChars, InsertStyledString, Undo, Redo, and SetStyleAt.
260 // SetStyleAt does not change the persistent state of a document
262 // Unlike Undo, Redo, and InsertStyledString, the pos argument is a cell number not a char number
263 void Document::DeleteChars(int pos
, int len
) {
264 if (enteredCount
== 0) {
267 NotifyModifyAttempt();
268 if (!cb
.IsReadOnly()) {
269 int prevLinesTotal
= LinesTotal();
270 bool startSavePoint
= cb
.IsSavePoint();
271 const char *text
= cb
.DeleteChars(pos
*2, len
* 2);
272 if (startSavePoint
&& cb
.IsCollectingUndo())
273 NotifySavePoint(!startSavePoint
);
275 int modFlags
= SC_MOD_DELETETEXT
| SC_PERFORMED_USER
;
276 DocModification
mh(modFlags
, pos
, len
, LinesTotal() - prevLinesTotal
, text
);
283 void Document::InsertStyledString(int position
, char *s
, int insertLength
) {
284 if (enteredCount
== 0) {
287 NotifyModifyAttempt();
288 if (!cb
.IsReadOnly()) {
289 int prevLinesTotal
= LinesTotal();
290 bool startSavePoint
= cb
.IsSavePoint();
291 const char *text
= cb
.InsertString(position
, s
, insertLength
);
292 if (startSavePoint
&& cb
.IsCollectingUndo())
293 NotifySavePoint(!startSavePoint
);
294 ModifiedAt(position
/ 2);
296 int modFlags
= SC_MOD_INSERTTEXT
| SC_PERFORMED_USER
;
297 DocModification
mh(modFlags
, position
/ 2, insertLength
/ 2, LinesTotal() - prevLinesTotal
, text
);
304 int Document::Undo() {
306 if (enteredCount
== 0) {
308 bool startSavePoint
= cb
.IsSavePoint();
309 int steps
= cb
.StartUndo();
310 for (int step
=0; step
<steps
; step
++) {
311 int prevLinesTotal
= LinesTotal();
312 const Action
&action
= cb
.UndoStep();
313 int cellPosition
= action
.position
/ 2;
314 ModifiedAt(cellPosition
);
315 newPos
= cellPosition
;
317 int modFlags
= SC_PERFORMED_UNDO
;
318 // With undo, an insertion action becomes a deletion notification
319 if (action
.at
== removeAction
) {
320 newPos
+= action
.lenData
;
321 modFlags
|= SC_MOD_INSERTTEXT
;
323 modFlags
|= SC_MOD_DELETETEXT
;
326 modFlags
|= SC_LASTSTEPINUNDOREDO
;
327 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
328 LinesTotal() - prevLinesTotal
, action
.data
));
331 bool endSavePoint
= cb
.IsSavePoint();
332 if (startSavePoint
!= endSavePoint
)
333 NotifySavePoint(endSavePoint
);
339 int Document::Redo() {
341 if (enteredCount
== 0) {
343 bool startSavePoint
= cb
.IsSavePoint();
344 int steps
= cb
.StartRedo();
345 for (int step
=0; step
<steps
; step
++) {
346 int prevLinesTotal
= LinesTotal();
347 const Action
&action
= cb
.RedoStep();
348 int cellPosition
= action
.position
/ 2;
349 ModifiedAt(cellPosition
);
350 newPos
= cellPosition
;
352 int modFlags
= SC_PERFORMED_REDO
;
353 if (action
.at
== insertAction
) {
354 newPos
+= action
.lenData
;
355 modFlags
|= SC_MOD_INSERTTEXT
;
357 modFlags
|= SC_MOD_DELETETEXT
;
360 modFlags
|= SC_LASTSTEPINUNDOREDO
;
361 NotifyModified(DocModification(modFlags
, cellPosition
, action
.lenData
,
362 LinesTotal() - prevLinesTotal
, action
.data
));
365 bool endSavePoint
= cb
.IsSavePoint();
366 if (startSavePoint
!= endSavePoint
)
367 NotifySavePoint(endSavePoint
);
373 void Document::InsertChar(int pos
, char ch
) {
377 InsertStyledString(pos
*2, chs
, 2);
380 // Insert a null terminated string
381 void Document::InsertString(int position
, const char *s
) {
382 InsertString(position
, s
, strlen(s
));
385 // Insert a string with a length
386 void Document::InsertString(int position
, const char *s
, int insertLength
) {
387 char *sWithStyle
= new char[insertLength
* 2];
389 for (int i
= 0; i
< insertLength
; i
++) {
390 sWithStyle
[i
*2] = s
[i
];
391 sWithStyle
[i
*2 + 1] = 0;
393 InsertStyledString(position
*2, sWithStyle
, insertLength
*2);
398 void Document::DelChar(int pos
) {
401 } else if (IsDBCS(pos
)) {
403 } else if (pos
< Length()) {
408 int Document::DelCharBack(int pos
) {
411 } else if (IsCrLf(pos
- 2)) {
412 DeleteChars(pos
- 2, 2);
414 } else if (IsDBCS(pos
- 1)) {
415 DeleteChars(pos
- 2, 2);
418 DeleteChars(pos
- 1, 1);
423 void Document::Indent(bool forwards
, int lineBottom
, int lineTop
) {
426 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
427 InsertChar(LineStart(line
), '\t');
430 // Dedent - suck white space off the front of the line to dedent by equivalent of a tab
431 for (int line
= lineBottom
; line
>= lineTop
; line
--) {
433 while (ispc
< tabInChars
&& cb
.CharAt(LineStart(line
) + ispc
) == ' ')
435 int posStartLine
= LineStart(line
);
436 if (ispc
== tabInChars
) {
437 DeleteChars(posStartLine
, ispc
);
438 } else if (cb
.CharAt(posStartLine
+ ispc
) == '\t') {
439 DeleteChars(posStartLine
, ispc
+ 1);
440 } else { // Hit a non-white
441 DeleteChars(posStartLine
, ispc
);
447 void Document::ConvertLineEnds(int eolModeSet
) {
449 for (int pos
= 0; pos
< Length(); pos
++) {
450 if (cb
.CharAt(pos
) == '\r') {
451 if (cb
.CharAt(pos
+1) == '\n') {
452 if (eolModeSet
!= SC_EOL_CRLF
) {
454 if (eolModeSet
== SC_EOL_CR
)
455 InsertString(pos
, "\r", 1);
457 InsertString(pos
, "\n", 1);
462 if (eolModeSet
!= SC_EOL_CR
) {
464 if (eolModeSet
== SC_EOL_CRLF
) {
465 InsertString(pos
, "\r\n", 2);
468 InsertString(pos
, "\n", 1);
472 } else if (cb
.CharAt(pos
) == '\n') {
473 if (eolModeSet
!= SC_EOL_LF
) {
475 if (eolModeSet
== SC_EOL_CRLF
) {
476 InsertString(pos
, "\r\n", 2);
479 InsertString(pos
, "\r", 1);
487 bool Document::IsWordChar(unsigned char ch
) {
488 return wordchars
[ch
];
491 int Document::ExtendWordSelect(int pos
, int delta
) {
493 while (pos
> 0 && IsWordChar(cb
.CharAt(pos
- 1)))
496 while (pos
< (Length()) && IsWordChar(cb
.CharAt(pos
)))
502 int Document::NextWordStart(int pos
, int delta
) {
504 while (pos
> 0 && (cb
.CharAt(pos
- 1) == ' ' || cb
.CharAt(pos
- 1) == '\t'))
506 if (isspace(cb
.CharAt(pos
- 1))) { // Back up to previous line
507 while (pos
> 0 && isspace(cb
.CharAt(pos
- 1)))
510 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
- 1));
511 while (pos
> 0 && !isspace(cb
.CharAt(pos
- 1)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
- 1))))
515 bool startAtWordChar
= IsWordChar(cb
.CharAt(pos
));
516 while (pos
< (Length()) && isspace(cb
.CharAt(pos
)))
518 while (pos
< (Length()) && !isspace(cb
.CharAt(pos
)) && (startAtWordChar
== IsWordChar(cb
.CharAt(pos
))))
520 while (pos
< (Length()) && (cb
.CharAt(pos
) == ' ' || cb
.CharAt(pos
) == '\t'))
526 bool Document::IsWordAt(int start
, int end
) {
527 int lengthDoc
= Length();
529 char ch
= CharAt(start
- 1);
533 if (end
< lengthDoc
- 1) {
534 char ch
= CharAt(end
);
541 // Find text in document, supporting both forward and backward
542 // searches (just pass minPos > maxPos to do a backward search)
543 // Has not been tested with backwards DBCS searches yet.
544 long Document::FindText(int minPos
, int maxPos
, const char *s
, bool caseSensitive
, bool word
) {
545 bool forward
= minPos
<= maxPos
;
546 int increment
= forward
? 1 : -1;
548 // Range endpoints should not be inside DBCS characters, but just in case, move them.
549 int startPos
= MovePositionOutsideChar(minPos
, increment
, false);
550 int endPos
= MovePositionOutsideChar(maxPos
, increment
, false);
552 // Compute actual search ranges needed
553 int lengthFind
= strlen(s
);
555 if (startPos
<= endPos
) {
556 endSearch
= endPos
- lengthFind
+ 1;
560 //Platform::DebugPrintf("Find %d %d %s %d\n", startPos, endPos, ft->lpstrText, lengthFind);
561 char firstChar
= s
[0];
563 firstChar
= toupper(firstChar
);
565 while (forward
? (pos
< endSearch
) : (pos
>= endSearch
)) {
566 char ch
= CharAt(pos
);
568 if (ch
== firstChar
) {
570 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
571 ch
= CharAt(pos
+ posMatch
);
572 if (ch
!= s
[posMatch
])
576 if ((!word
) || IsWordAt(pos
, pos
+ lengthFind
))
581 if (toupper(ch
) == firstChar
) {
583 for (int posMatch
= 1; posMatch
< lengthFind
&& found
; posMatch
++) {
584 ch
= CharAt(pos
+ posMatch
);
585 if (toupper(ch
) != toupper(s
[posMatch
]))
589 if ((!word
) || IsWordAt(pos
, pos
+ lengthFind
))
596 // Ensure trying to match from start of character
597 pos
= MovePositionOutsideChar(pos
, increment
, false);
600 //Platform::DebugPrintf("Not found\n");
604 int Document::LinesTotal() {
608 void Document::SetWordChars(unsigned char *chars
) {
610 for (ch
= 0; ch
< 256; ch
++) {
611 wordchars
[ch
] = false;
615 wordchars
[*chars
] = true;
619 for (ch
= 0; ch
< 256; ch
++) {
620 wordchars
[ch
] = isalnum(ch
) || ch
== '_';
625 void Document::SetStylingBits(int bits
) {
628 for (int bit
=0; bit
<stylingBits
; bit
++) {
629 stylingBitsMask
<<= 1;
630 stylingBitsMask
|= 1;
634 void Document::StartStyling(int position
, char mask
) {
635 stylingPos
= position
;
639 void Document::SetStyleFor(int length
, char style
) {
640 if (enteredCount
== 0) {
642 int prevEndStyled
= endStyled
;
643 if (cb
.SetStyleFor(stylingPos
, length
, style
, stylingMask
)) {
644 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
645 prevEndStyled
, length
);
648 stylingPos
+= length
;
649 endStyled
= stylingPos
;
654 void Document::SetStyles(int length
, char *styles
) {
655 if (enteredCount
== 0) {
657 int prevEndStyled
= endStyled
;
658 bool didChange
= false;
659 for (int iPos
= 0; iPos
< length
; iPos
++, stylingPos
++) {
660 if (cb
.SetStyleAt(stylingPos
, styles
[iPos
], stylingMask
)) {
664 endStyled
= stylingPos
;
666 DocModification
mh(SC_MOD_CHANGESTYLE
| SC_PERFORMED_USER
,
667 prevEndStyled
, endStyled
- prevEndStyled
);
674 bool Document::AddWatcher(DocWatcher
*watcher
, void *userData
) {
675 for (int i
= 0; i
< lenWatchers
; i
++) {
676 if ((watchers
[i
].watcher
== watcher
) &&
677 (watchers
[i
].userData
== userData
))
680 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
+ 1];
683 for (int j
= 0; j
< lenWatchers
; j
++)
684 pwNew
[j
] = watchers
[j
];
685 pwNew
[lenWatchers
].watcher
= watcher
;
686 pwNew
[lenWatchers
].userData
= userData
;
693 bool Document::RemoveWatcher(DocWatcher
*watcher
, void *userData
) {
694 for (int i
= 0; i
< lenWatchers
; i
++) {
695 if ((watchers
[i
].watcher
== watcher
) &&
696 (watchers
[i
].userData
== userData
)) {
697 if (lenWatchers
== 1) {
702 WatcherWithUserData
*pwNew
= new WatcherWithUserData
[lenWatchers
];
705 for (int j
= 0; j
< lenWatchers
- 1; j
++) {
706 pwNew
[j
] = (j
< i
) ? watchers
[j
] : watchers
[j
+ 1];
718 void Document::NotifyModifyAttempt() {
719 for (int i
= 0; i
< lenWatchers
; i
++) {
720 watchers
[i
].watcher
->NotifyModifyAttempt(this, watchers
[i
].userData
);
724 void Document::NotifySavePoint(bool atSavePoint
) {
725 for (int i
= 0; i
< lenWatchers
; i
++) {
726 watchers
[i
].watcher
->NotifySavePoint(this, watchers
[i
].userData
, atSavePoint
);
730 void Document::NotifyModified(DocModification mh
) {
731 for (int i
= 0; i
< lenWatchers
; i
++) {
732 watchers
[i
].watcher
->NotifyModified(this, mh
, watchers
[i
].userData
);