Add an UI to enable/disable specific overlay handlers.
[TortoiseGit.git] / ext / scintilla / src / LexPython.cxx
blob3ac0b75a01eb830367aabab249282fb0edd2811e
1 // Scintilla source code edit control
2 /** @file LexPython.cxx
3 ** Lexer for Python.
4 **/
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.
8 #include <stdlib.h>
9 #include <string.h>
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <stdarg.h>
14 #include "Platform.h"
16 #include "PropSet.h"
17 #include "Accessor.h"
18 #include "StyleContext.h"
19 #include "KeyWords.h"
20 #include "Scintilla.h"
21 #include "SciLexer.h"
23 #ifdef SCI_NAMESPACE
24 using namespace Scintilla;
25 #endif
27 enum kwType { kwOther, kwClass, kwDef, kwImport };
28 static const int indicatorWhitespace = 1;
30 static bool IsPyComment(Accessor &styler, int pos, int len) {
31 return len > 0 && styler[pos] == '#';
34 static bool IsPyStringStart(int ch, int chNext, int chNext2) {
35 if (ch == '\'' || ch == '"')
36 return true;
37 if (ch == 'u' || ch == 'U') {
38 if (chNext == '"' || chNext == '\'')
39 return true;
40 if ((chNext == 'r' || chNext == 'R') && (chNext2 == '"' || chNext2 == '\''))
41 return true;
43 if ((ch == 'r' || ch == 'R') && (chNext == '"' || chNext == '\''))
44 return true;
46 return false;
49 /* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */
50 static int GetPyStringState(Accessor &styler, int i, unsigned int *nextIndex) {
51 char ch = styler.SafeGetCharAt(i);
52 char chNext = styler.SafeGetCharAt(i + 1);
54 // Advance beyond r, u, or ur prefix, but bail if there are any unexpected chars
55 if (ch == 'r' || ch == 'R') {
56 i++;
57 ch = styler.SafeGetCharAt(i);
58 chNext = styler.SafeGetCharAt(i + 1);
59 } else if (ch == 'u' || ch == 'U') {
60 if (chNext == 'r' || chNext == 'R')
61 i += 2;
62 else
63 i += 1;
64 ch = styler.SafeGetCharAt(i);
65 chNext = styler.SafeGetCharAt(i + 1);
68 if (ch != '"' && ch != '\'') {
69 *nextIndex = i + 1;
70 return SCE_P_DEFAULT;
73 if (ch == chNext && ch == styler.SafeGetCharAt(i + 2)) {
74 *nextIndex = i + 3;
76 if (ch == '"')
77 return SCE_P_TRIPLEDOUBLE;
78 else
79 return SCE_P_TRIPLE;
80 } else {
81 *nextIndex = i + 1;
83 if (ch == '"')
84 return SCE_P_STRING;
85 else
86 return SCE_P_CHARACTER;
90 static inline bool IsAWordChar(int ch) {
91 return (ch < 0x80) && (isalnum(ch) || ch == '.' || ch == '_');
94 static inline bool IsAWordStart(int ch) {
95 return (ch < 0x80) && (isalnum(ch) || ch == '_');
98 static void ColourisePyDoc(unsigned int startPos, int length, int initStyle,
99 WordList *keywordlists[], Accessor &styler) {
101 int endPos = startPos + length;
103 // Backtrack to previous line in case need to fix its tab whinging
104 int lineCurrent = styler.GetLine(startPos);
105 if (startPos > 0) {
106 if (lineCurrent > 0) {
107 lineCurrent--;
108 startPos = styler.LineStart(lineCurrent);
109 if (startPos == 0)
110 initStyle = SCE_P_DEFAULT;
111 else
112 initStyle = styler.StyleAt(startPos - 1);
116 WordList &keywords = *keywordlists[0];
117 WordList &keywords2 = *keywordlists[1];
119 const int whingeLevel = styler.GetPropertyInt("tab.timmy.whinge.level");
121 initStyle = initStyle & 31;
122 if (initStyle == SCE_P_STRINGEOL) {
123 initStyle = SCE_P_DEFAULT;
126 kwType kwLast = kwOther;
127 int spaceFlags = 0;
128 styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);
129 bool hexadecimal = false;
131 StyleContext sc(startPos, endPos - startPos, initStyle, styler);
133 bool indentGood = true;
134 int startIndicator = sc.currentPos;
136 for (; sc.More(); sc.Forward()) {
138 if (sc.atLineStart) {
139 styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);
140 indentGood = true;
141 if (whingeLevel == 1) {
142 indentGood = (spaceFlags & wsInconsistent) == 0;
143 } else if (whingeLevel == 2) {
144 indentGood = (spaceFlags & wsSpaceTab) == 0;
145 } else if (whingeLevel == 3) {
146 indentGood = (spaceFlags & wsSpace) == 0;
147 } else if (whingeLevel == 4) {
148 indentGood = (spaceFlags & wsTab) == 0;
150 if (!indentGood) {
151 styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0);
152 startIndicator = sc.currentPos;
156 if (sc.atLineEnd) {
157 if ((sc.state == SCE_P_DEFAULT) ||
158 (sc.state == SCE_P_TRIPLE) ||
159 (sc.state == SCE_P_TRIPLEDOUBLE)) {
160 // Perform colourisation of white space and triple quoted strings at end of each line to allow
161 // tab marking to work inside white space and triple quoted strings
162 sc.SetState(sc.state);
164 lineCurrent++;
165 if ((sc.state == SCE_P_STRING) || (sc.state == SCE_P_CHARACTER)) {
166 sc.ChangeState(SCE_P_STRINGEOL);
167 sc.ForwardSetState(SCE_P_DEFAULT);
169 if (!sc.More())
170 break;
173 bool needEOLCheck = false;
175 // Check for a state end
176 if (sc.state == SCE_P_OPERATOR) {
177 kwLast = kwOther;
178 sc.SetState(SCE_P_DEFAULT);
179 } else if (sc.state == SCE_P_NUMBER) {
180 if (!IsAWordChar(sc.ch) &&
181 !(!hexadecimal && ((sc.ch == '+' || sc.ch == '-') && (sc.chPrev == 'e' || sc.chPrev == 'E')))) {
182 sc.SetState(SCE_P_DEFAULT);
184 } else if (sc.state == SCE_P_IDENTIFIER) {
185 if ((sc.ch == '.') || (!IsAWordChar(sc.ch))) {
186 char s[100];
187 sc.GetCurrent(s, sizeof(s));
188 int style = SCE_P_IDENTIFIER;
189 if ((kwLast == kwImport) && (strcmp(s, "as") == 0)) {
190 style = SCE_P_WORD;
191 } else if (keywords.InList(s)) {
192 style = SCE_P_WORD;
193 } else if (kwLast == kwClass) {
194 style = SCE_P_CLASSNAME;
195 } else if (kwLast == kwDef) {
196 style = SCE_P_DEFNAME;
197 } else if (keywords2.InList(s)) {
198 style = SCE_P_WORD2;
200 sc.ChangeState(style);
201 sc.SetState(SCE_P_DEFAULT);
202 if (style == SCE_P_WORD) {
203 if (0 == strcmp(s, "class"))
204 kwLast = kwClass;
205 else if (0 == strcmp(s, "def"))
206 kwLast = kwDef;
207 else if (0 == strcmp(s, "import"))
208 kwLast = kwImport;
209 else
210 kwLast = kwOther;
211 } else {
212 kwLast = kwOther;
215 } else if ((sc.state == SCE_P_COMMENTLINE) || (sc.state == SCE_P_COMMENTBLOCK)) {
216 if (sc.ch == '\r' || sc.ch == '\n') {
217 sc.SetState(SCE_P_DEFAULT);
219 } else if (sc.state == SCE_P_DECORATOR) {
220 if (!IsAWordChar(sc.ch)) {
221 sc.SetState(SCE_P_DEFAULT);
223 } else if ((sc.state == SCE_P_STRING) || (sc.state == SCE_P_CHARACTER)) {
224 if (sc.ch == '\\') {
225 if ((sc.chNext == '\r') && (sc.GetRelative(2) == '\n')) {
226 sc.Forward();
228 sc.Forward();
229 } else if ((sc.state == SCE_P_STRING) && (sc.ch == '\"')) {
230 sc.ForwardSetState(SCE_P_DEFAULT);
231 needEOLCheck = true;
232 } else if ((sc.state == SCE_P_CHARACTER) && (sc.ch == '\'')) {
233 sc.ForwardSetState(SCE_P_DEFAULT);
234 needEOLCheck = true;
236 } else if (sc.state == SCE_P_TRIPLE) {
237 if (sc.ch == '\\') {
238 sc.Forward();
239 } else if (sc.Match("\'\'\'")) {
240 sc.Forward();
241 sc.Forward();
242 sc.ForwardSetState(SCE_P_DEFAULT);
243 needEOLCheck = true;
245 } else if (sc.state == SCE_P_TRIPLEDOUBLE) {
246 if (sc.ch == '\\') {
247 sc.Forward();
248 } else if (sc.Match("\"\"\"")) {
249 sc.Forward();
250 sc.Forward();
251 sc.ForwardSetState(SCE_P_DEFAULT);
252 needEOLCheck = true;
256 if (!indentGood && !IsASpaceOrTab(sc.ch)) {
257 styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 1);
258 startIndicator = sc.currentPos;
259 indentGood = true;
262 // State exit code may have moved on to end of line
263 if (needEOLCheck && sc.atLineEnd) {
264 lineCurrent++;
265 styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);
266 if (!sc.More())
267 break;
270 // Check for a new state starting character
271 if (sc.state == SCE_P_DEFAULT) {
272 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
273 if (sc.ch == '0' && (sc.chNext == 'x' || sc.chNext == 'X')) {
274 hexadecimal = true;
275 } else {
276 hexadecimal = false;
278 sc.SetState(SCE_P_NUMBER);
279 } else if (isascii(sc.ch) && isoperator(static_cast<char>(sc.ch)) || sc.ch == '`') {
280 sc.SetState(SCE_P_OPERATOR);
281 } else if (sc.ch == '#') {
282 sc.SetState(sc.chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE);
283 } else if (sc.ch == '@') {
284 sc.SetState(SCE_P_DECORATOR);
285 } else if (IsPyStringStart(sc.ch, sc.chNext, sc.GetRelative(2))) {
286 unsigned int nextIndex = 0;
287 sc.SetState(GetPyStringState(styler, sc.currentPos, &nextIndex));
288 while (nextIndex > (sc.currentPos + 1) && sc.More()) {
289 sc.Forward();
291 } else if (IsAWordStart(sc.ch)) {
292 sc.SetState(SCE_P_IDENTIFIER);
296 styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0);
297 sc.Complete();
300 static bool IsCommentLine(int line, Accessor &styler) {
301 int pos = styler.LineStart(line);
302 int eol_pos = styler.LineStart(line + 1) - 1;
303 for (int i = pos; i < eol_pos; i++) {
304 char ch = styler[i];
305 if (ch == '#')
306 return true;
307 else if (ch != ' ' && ch != '\t')
308 return false;
310 return false;
313 static bool IsQuoteLine(int line, Accessor &styler) {
314 int style = styler.StyleAt(styler.LineStart(line)) & 31;
315 return ((style == SCE_P_TRIPLE) || (style == SCE_P_TRIPLEDOUBLE));
319 static void FoldPyDoc(unsigned int startPos, int length, int /*initStyle - unused*/,
320 WordList *[], Accessor &styler) {
321 const int maxPos = startPos + length;
322 const int maxLines = styler.GetLine(maxPos - 1); // Requested last line
323 const int docLines = styler.GetLine(styler.Length() - 1); // Available last line
324 const bool foldComment = styler.GetPropertyInt("fold.comment.python") != 0;
325 const bool foldQuotes = styler.GetPropertyInt("fold.quotes.python") != 0;
327 // Backtrack to previous non-blank line so we can determine indent level
328 // for any white space lines (needed esp. within triple quoted strings)
329 // and so we can fix any preceding fold level (which is why we go back
330 // at least one line in all cases)
331 int spaceFlags = 0;
332 int lineCurrent = styler.GetLine(startPos);
333 int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
334 while (lineCurrent > 0) {
335 lineCurrent--;
336 indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
337 if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG) &&
338 (!IsCommentLine(lineCurrent, styler)) &&
339 (!IsQuoteLine(lineCurrent, styler)))
340 break;
342 int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
344 // Set up initial loop state
345 startPos = styler.LineStart(lineCurrent);
346 int prev_state = SCE_P_DEFAULT & 31;
347 if (lineCurrent >= 1)
348 prev_state = styler.StyleAt(startPos - 1) & 31;
349 int prevQuote = foldQuotes && ((prev_state == SCE_P_TRIPLE) || (prev_state == SCE_P_TRIPLEDOUBLE));
350 int prevComment = 0;
351 if (lineCurrent >= 1)
352 prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler);
354 // Process all characters to end of requested range or end of any triple quote
355 // or comment that hangs over the end of the range. Cap processing in all cases
356 // to end of document (in case of unclosed quote or comment at end).
357 while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevQuote || prevComment)) {
359 // Gather info
360 int lev = indentCurrent;
361 int lineNext = lineCurrent + 1;
362 int indentNext = indentCurrent;
363 int quote = false;
364 if (lineNext <= docLines) {
365 // Information about next line is only available if not at end of document
366 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
367 int style = styler.StyleAt(styler.LineStart(lineNext)) & 31;
368 quote = foldQuotes && ((style == SCE_P_TRIPLE) || (style == SCE_P_TRIPLEDOUBLE));
370 const int quote_start = (quote && !prevQuote);
371 const int quote_continue = (quote && prevQuote);
372 const int comment = foldComment && IsCommentLine(lineCurrent, styler);
373 const int comment_start = (comment && !prevComment && (lineNext <= docLines) &&
374 IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE));
375 const int comment_continue = (comment && prevComment);
376 if ((!quote || !prevQuote) && !comment)
377 indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
378 if (quote)
379 indentNext = indentCurrentLevel;
380 if (indentNext & SC_FOLDLEVELWHITEFLAG)
381 indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
383 if (quote_start) {
384 // Place fold point at start of triple quoted string
385 lev |= SC_FOLDLEVELHEADERFLAG;
386 } else if (quote_continue || prevQuote) {
387 // Add level to rest of lines in the string
388 lev = lev + 1;
389 } else if (comment_start) {
390 // Place fold point at start of a block of comments
391 lev |= SC_FOLDLEVELHEADERFLAG;
392 } else if (comment_continue) {
393 // Add level to rest of lines in the block
394 lev = lev + 1;
397 // Skip past any blank lines for next indent level info; we skip also
398 // comments (all comments, not just those starting in column 0)
399 // which effectively folds them into surrounding code rather
400 // than screwing up folding.
402 while (!quote &&
403 (lineNext < docLines) &&
404 ((indentNext & SC_FOLDLEVELWHITEFLAG) ||
405 (lineNext <= docLines && IsCommentLine(lineNext, styler)))) {
407 lineNext++;
408 indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
411 const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK;
412 const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments);
414 // Now set all the indent levels on the lines we skipped
415 // Do this from end to start. Once we encounter one line
416 // which is indented more than the line after the end of
417 // the comment-block, use the level of the block before
419 int skipLine = lineNext;
420 int skipLevel = levelAfterComments;
422 while (--skipLine > lineCurrent) {
423 int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL);
425 if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)
426 skipLevel = levelBeforeComments;
428 int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
430 styler.SetLevel(skipLine, skipLevel | whiteFlag);
433 // Set fold header on non-quote/non-comment line
434 if (!quote && !comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG) ) {
435 if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK))
436 lev |= SC_FOLDLEVELHEADERFLAG;
439 // Keep track of triple quote and block comment state of previous line
440 prevQuote = quote;
441 prevComment = comment_start || comment_continue;
443 // Set fold level for this line and move to next line
444 styler.SetLevel(lineCurrent, lev);
445 indentCurrent = indentNext;
446 lineCurrent = lineNext;
449 // NOTE: Cannot set level of last line here because indentCurrent doesn't have
450 // header flag set; the loop above is crafted to take care of this case!
451 //styler.SetLevel(lineCurrent, indentCurrent);
454 static const char * const pythonWordListDesc[] = {
455 "Keywords",
456 "Highlighted identifiers",
460 LexerModule lmPython(SCLEX_PYTHON, ColourisePyDoc, "python", FoldPyDoc,
461 pythonWordListDesc);