]>
git.saurik.com Git - wxWidgets.git/blob - src/stc/scintilla/src/LexPython.cxx
1 // Scintilla source code edit control
2 /** @file LexPython.cxx
5 // Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
18 #include "StyleContext.h"
20 #include "Scintilla.h"
23 enum kwType
{ kwOther
, kwClass
, kwDef
, kwImport
};
25 static bool IsPyComment(Accessor
&styler
, int pos
, int len
) {
26 return len
> 0 && styler
[pos
] == '#';
29 static bool IsPyStringStart(int ch
, int chNext
, int chNext2
) {
30 if (ch
== '\'' || ch
== '"')
32 if (ch
== 'u' || ch
== 'U') {
33 if (chNext
== '"' || chNext
== '\'')
35 if ((chNext
== 'r' || chNext
== 'R') && (chNext2
== '"' || chNext2
== '\''))
38 if ((ch
== 'r' || ch
== 'R') && (chNext
== '"' || chNext
== '\''))
44 /* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */
45 static int GetPyStringState(Accessor
&styler
, int i
, unsigned int *nextIndex
) {
46 char ch
= styler
.SafeGetCharAt(i
);
47 char chNext
= styler
.SafeGetCharAt(i
+ 1);
49 // Advance beyond r, u, or ur prefix, but bail if there are any unexpected chars
50 if (ch
== 'r' || ch
== 'R') {
52 ch
= styler
.SafeGetCharAt(i
);
53 chNext
= styler
.SafeGetCharAt(i
+ 1);
54 } else if (ch
== 'u' || ch
== 'U') {
55 if (chNext
== 'r' || chNext
== 'R')
59 ch
= styler
.SafeGetCharAt(i
);
60 chNext
= styler
.SafeGetCharAt(i
+ 1);
63 if (ch
!= '"' && ch
!= '\'') {
68 if (ch
== chNext
&& ch
== styler
.SafeGetCharAt(i
+ 2)) {
72 return SCE_P_TRIPLEDOUBLE
;
81 return SCE_P_CHARACTER
;
85 static inline bool IsAWordChar(int ch
) {
86 return (ch
< 0x80) && (isalnum(ch
) || ch
== '.' || ch
== '_');
89 static inline bool IsAWordStart(int ch
) {
90 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_');
93 static void ColourisePyDoc(unsigned int startPos
, int length
, int initStyle
,
94 WordList
*keywordlists
[], Accessor
&styler
) {
96 int endPos
= startPos
+ length
;
98 // Backtrack to previous line in case need to fix its tab whinging
99 int lineCurrent
= styler
.GetLine(startPos
);
101 if (lineCurrent
> 0) {
103 startPos
= styler
.LineStart(lineCurrent
);
105 initStyle
= SCE_P_DEFAULT
;
107 initStyle
= styler
.StyleAt(startPos
- 1);
111 WordList
&keywords
= *keywordlists
[0];
112 WordList
&keywords2
= *keywordlists
[1];
114 const int whingeLevel
= styler
.GetPropertyInt("tab.timmy.whinge.level");
116 initStyle
= initStyle
& 31;
117 if (initStyle
== SCE_P_STRINGEOL
) {
118 initStyle
= SCE_P_DEFAULT
;
121 kwType kwLast
= kwOther
;
123 styler
.IndentAmount(lineCurrent
, &spaceFlags
, IsPyComment
);
124 bool hexadecimal
= false;
126 // Python uses a different mask because bad indentation is marked by oring with 32
127 StyleContext
sc(startPos
, endPos
- startPos
, initStyle
, styler
, 0x7f);
129 for (; sc
.More(); sc
.Forward()) {
131 if (sc
.atLineStart
) {
132 const char chBad
= static_cast<char>(64);
133 const char chGood
= static_cast<char>(0);
134 char chFlags
= chGood
;
135 if (whingeLevel
== 1) {
136 chFlags
= (spaceFlags
& wsInconsistent
) ? chBad
: chGood
;
137 } else if (whingeLevel
== 2) {
138 chFlags
= (spaceFlags
& wsSpaceTab
) ? chBad
: chGood
;
139 } else if (whingeLevel
== 3) {
140 chFlags
= (spaceFlags
& wsSpace
) ? chBad
: chGood
;
141 } else if (whingeLevel
== 4) {
142 chFlags
= (spaceFlags
& wsTab
) ? chBad
: chGood
;
144 sc
.SetState(sc
.state
);
145 styler
.SetFlags(chFlags
, static_cast<char>(sc
.state
));
149 if ((sc
.state
== SCE_P_DEFAULT
) ||
150 (sc
.state
== SCE_P_TRIPLE
) ||
151 (sc
.state
== SCE_P_TRIPLEDOUBLE
)) {
152 // Perform colourisation of white space and triple quoted strings at end of each line to allow
153 // tab marking to work inside white space and triple quoted strings
154 sc
.SetState(sc
.state
);
157 styler
.IndentAmount(lineCurrent
, &spaceFlags
, IsPyComment
);
158 if ((sc
.state
== SCE_P_STRING
) || (sc
.state
== SCE_P_CHARACTER
)) {
159 sc
.ChangeState(SCE_P_STRINGEOL
);
160 sc
.ForwardSetState(SCE_P_DEFAULT
);
166 bool needEOLCheck
= false;
168 // Check for a state end
169 if (sc
.state
== SCE_P_OPERATOR
) {
171 sc
.SetState(SCE_P_DEFAULT
);
172 } else if (sc
.state
== SCE_P_NUMBER
) {
173 if (!IsAWordChar(sc
.ch
) &&
174 !(!hexadecimal
&& ((sc
.ch
== '+' || sc
.ch
== '-') && (sc
.chPrev
== 'e' || sc
.chPrev
== 'E')))) {
175 sc
.SetState(SCE_P_DEFAULT
);
177 } else if (sc
.state
== SCE_P_IDENTIFIER
) {
178 if ((sc
.ch
== '.') || (!IsAWordChar(sc
.ch
))) {
180 sc
.GetCurrent(s
, sizeof(s
));
181 int style
= SCE_P_IDENTIFIER
;
182 if ((kwLast
== kwImport
) && (strcmp(s
, "as") == 0)) {
184 } else if (keywords
.InList(s
)) {
186 } else if (kwLast
== kwClass
) {
187 style
= SCE_P_CLASSNAME
;
188 } else if (kwLast
== kwDef
) {
189 style
= SCE_P_DEFNAME
;
190 } else if (keywords2
.InList(s
)) {
193 sc
.ChangeState(style
);
194 sc
.SetState(SCE_P_DEFAULT
);
195 if (style
== SCE_P_WORD
) {
196 if (0 == strcmp(s
, "class"))
198 else if (0 == strcmp(s
, "def"))
200 else if (0 == strcmp(s
, "import"))
208 } else if ((sc
.state
== SCE_P_COMMENTLINE
) || (sc
.state
== SCE_P_COMMENTBLOCK
)) {
209 if (sc
.ch
== '\r' || sc
.ch
== '\n') {
210 sc
.SetState(SCE_P_DEFAULT
);
212 } else if (sc
.state
== SCE_P_DECORATOR
) {
213 if (sc
.ch
== '\r' || sc
.ch
== '\n') {
214 sc
.SetState(SCE_P_DEFAULT
);
216 } else if ((sc
.state
== SCE_P_STRING
) || (sc
.state
== SCE_P_CHARACTER
)) {
218 if ((sc
.chNext
== '\r') && (sc
.GetRelative(2) == '\n')) {
222 } else if ((sc
.state
== SCE_P_STRING
) && (sc
.ch
== '\"')) {
223 sc
.ForwardSetState(SCE_P_DEFAULT
);
225 } else if ((sc
.state
== SCE_P_CHARACTER
) && (sc
.ch
== '\'')) {
226 sc
.ForwardSetState(SCE_P_DEFAULT
);
229 } else if (sc
.state
== SCE_P_TRIPLE
) {
232 } else if (sc
.Match("\'\'\'")) {
235 sc
.ForwardSetState(SCE_P_DEFAULT
);
238 } else if (sc
.state
== SCE_P_TRIPLEDOUBLE
) {
241 } else if (sc
.Match("\"\"\"")) {
244 sc
.ForwardSetState(SCE_P_DEFAULT
);
249 // State exit code may have moved on to end of line
250 if (needEOLCheck
&& sc
.atLineEnd
) {
252 styler
.IndentAmount(lineCurrent
, &spaceFlags
, IsPyComment
);
257 // Check for a new state starting character
258 if (sc
.state
== SCE_P_DEFAULT
) {
259 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
260 if (sc
.ch
== '0' && (sc
.chNext
== 'x' || sc
.chNext
== 'X')) {
265 sc
.SetState(SCE_P_NUMBER
);
266 } else if (isascii(sc
.ch
) && isoperator(static_cast<char>(sc
.ch
)) || sc
.ch
== '`') {
267 sc
.SetState(SCE_P_OPERATOR
);
268 } else if (sc
.ch
== '#') {
269 sc
.SetState(sc
.chNext
== '#' ? SCE_P_COMMENTBLOCK
: SCE_P_COMMENTLINE
);
270 } else if (sc
.ch
== '@') {
271 sc
.SetState(SCE_P_DECORATOR
);
272 } else if (IsPyStringStart(sc
.ch
, sc
.chNext
, sc
.GetRelative(2))) {
273 unsigned int nextIndex
= 0;
274 sc
.SetState(GetPyStringState(styler
, sc
.currentPos
, &nextIndex
));
275 while (nextIndex
> (sc
.currentPos
+ 1) && sc
.More()) {
278 } else if (IsAWordStart(sc
.ch
)) {
279 sc
.SetState(SCE_P_IDENTIFIER
);
286 static bool IsCommentLine(int line
, Accessor
&styler
) {
287 int pos
= styler
.LineStart(line
);
288 int eol_pos
= styler
.LineStart(line
+ 1) - 1;
289 for (int i
= pos
; i
< eol_pos
; i
++) {
293 else if (ch
!= ' ' && ch
!= '\t')
299 static bool IsQuoteLine(int line
, Accessor
&styler
) {
300 int style
= styler
.StyleAt(styler
.LineStart(line
)) & 31;
301 return ((style
== SCE_P_TRIPLE
) || (style
== SCE_P_TRIPLEDOUBLE
));
305 static void FoldPyDoc(unsigned int startPos
, int length
, int /*initStyle - unused*/,
306 WordList
*[], Accessor
&styler
) {
307 const int maxPos
= startPos
+ length
;
308 const int maxLines
= styler
.GetLine(maxPos
- 1); // Requested last line
309 const int docLines
= styler
.GetLine(styler
.Length() - 1); // Available last line
310 const bool foldComment
= styler
.GetPropertyInt("fold.comment.python") != 0;
311 const bool foldQuotes
= styler
.GetPropertyInt("fold.quotes.python") != 0;
313 // Backtrack to previous non-blank line so we can determine indent level
314 // for any white space lines (needed esp. within triple quoted strings)
315 // and so we can fix any preceding fold level (which is why we go back
316 // at least one line in all cases)
318 int lineCurrent
= styler
.GetLine(startPos
);
319 int indentCurrent
= styler
.IndentAmount(lineCurrent
, &spaceFlags
, NULL
);
320 while (lineCurrent
> 0) {
322 indentCurrent
= styler
.IndentAmount(lineCurrent
, &spaceFlags
, NULL
);
323 if (!(indentCurrent
& SC_FOLDLEVELWHITEFLAG
) &&
324 (!IsCommentLine(lineCurrent
, styler
)) &&
325 (!IsQuoteLine(lineCurrent
, styler
)))
328 int indentCurrentLevel
= indentCurrent
& SC_FOLDLEVELNUMBERMASK
;
330 // Set up initial loop state
331 startPos
= styler
.LineStart(lineCurrent
);
332 int prev_state
= SCE_P_DEFAULT
& 31;
333 if (lineCurrent
>= 1)
334 prev_state
= styler
.StyleAt(startPos
- 1) & 31;
335 int prevQuote
= foldQuotes
&& ((prev_state
== SCE_P_TRIPLE
) || (prev_state
== SCE_P_TRIPLEDOUBLE
));
337 if (lineCurrent
>= 1)
338 prevComment
= foldComment
&& IsCommentLine(lineCurrent
- 1, styler
);
340 // Process all characters to end of requested range or end of any triple quote
341 // or comment that hangs over the end of the range. Cap processing in all cases
342 // to end of document (in case of unclosed quote or comment at end).
343 while ((lineCurrent
<= docLines
) && ((lineCurrent
<= maxLines
) || prevQuote
|| prevComment
)) {
346 int lev
= indentCurrent
;
347 int lineNext
= lineCurrent
+ 1;
348 int indentNext
= indentCurrent
;
350 if (lineNext
<= docLines
) {
351 // Information about next line is only available if not at end of document
352 indentNext
= styler
.IndentAmount(lineNext
, &spaceFlags
, NULL
);
353 int style
= styler
.StyleAt(styler
.LineStart(lineNext
)) & 31;
354 quote
= foldQuotes
&& ((style
== SCE_P_TRIPLE
) || (style
== SCE_P_TRIPLEDOUBLE
));
356 const int quote_start
= (quote
&& !prevQuote
);
357 const int quote_continue
= (quote
&& prevQuote
);
358 const int comment
= foldComment
&& IsCommentLine(lineCurrent
, styler
);
359 const int comment_start
= (comment
&& !prevComment
&& (lineNext
<= docLines
) &&
360 IsCommentLine(lineNext
, styler
) && (lev
> SC_FOLDLEVELBASE
));
361 const int comment_continue
= (comment
&& prevComment
);
362 if ((!quote
|| !prevQuote
) && !comment
)
363 indentCurrentLevel
= indentCurrent
& SC_FOLDLEVELNUMBERMASK
;
365 indentNext
= indentCurrentLevel
;
366 if (indentNext
& SC_FOLDLEVELWHITEFLAG
)
367 indentNext
= SC_FOLDLEVELWHITEFLAG
| indentCurrentLevel
;
370 // Place fold point at start of triple quoted string
371 lev
|= SC_FOLDLEVELHEADERFLAG
;
372 } else if (quote_continue
|| prevQuote
) {
373 // Add level to rest of lines in the string
375 } else if (comment_start
) {
376 // Place fold point at start of a block of comments
377 lev
|= SC_FOLDLEVELHEADERFLAG
;
378 } else if (comment_continue
) {
379 // Add level to rest of lines in the block
383 // Skip past any blank lines for next indent level info; we skip also
384 // comments (all comments, not just those starting in column 0)
385 // which effectively folds them into surrounding code rather
386 // than screwing up folding.
389 (lineNext
< docLines
) &&
390 ((indentNext
& SC_FOLDLEVELWHITEFLAG
) ||
391 (lineNext
<= docLines
&& IsCommentLine(lineNext
, styler
)))) {
394 indentNext
= styler
.IndentAmount(lineNext
, &spaceFlags
, NULL
);
397 const int levelAfterComments
= indentNext
& SC_FOLDLEVELNUMBERMASK
;
398 const int levelBeforeComments
= Platform::Maximum(indentCurrentLevel
,levelAfterComments
);
400 // Now set all the indent levels on the lines we skipped
401 // Do this from end to start. Once we encounter one line
402 // which is indented more than the line after the end of
403 // the comment-block, use the level of the block before
405 int skipLine
= lineNext
;
406 int skipLevel
= levelAfterComments
;
408 while (--skipLine
> lineCurrent
) {
409 int skipLineIndent
= styler
.IndentAmount(skipLine
, &spaceFlags
, NULL
);
411 if ((skipLineIndent
& SC_FOLDLEVELNUMBERMASK
) > levelAfterComments
)
412 skipLevel
= levelBeforeComments
;
414 int whiteFlag
= skipLineIndent
& SC_FOLDLEVELWHITEFLAG
;
416 styler
.SetLevel(skipLine
, skipLevel
| whiteFlag
);
419 // Set fold header on non-quote/non-comment line
420 if (!quote
&& !comment
&& !(indentCurrent
& SC_FOLDLEVELWHITEFLAG
) ) {
421 if ((indentCurrent
& SC_FOLDLEVELNUMBERMASK
) < (indentNext
& SC_FOLDLEVELNUMBERMASK
))
422 lev
|= SC_FOLDLEVELHEADERFLAG
;
425 // Keep track of triple quote and block comment state of previous line
427 prevComment
= comment_start
|| comment_continue
;
429 // Set fold level for this line and move to next line
430 styler
.SetLevel(lineCurrent
, lev
);
431 indentCurrent
= indentNext
;
432 lineCurrent
= lineNext
;
435 // NOTE: Cannot set level of last line here because indentCurrent doesn't have
436 // header flag set; the loop above is crafted to take care of this case!
437 //styler.SetLevel(lineCurrent, indentCurrent);
440 static const char * const pythonWordListDesc
[] = {
442 "Highlighted identifiers",
446 LexerModule
lmPython(SCLEX_PYTHON
, ColourisePyDoc
, "python", FoldPyDoc
,