--- /dev/null
+// Scintilla source code edit control\r
+/** @file LexPython.cxx\r
+ ** Lexer for Python.\r
+ **/\r
+// Copyright 1998-2002 by Neil Hodgson <neilh@scintilla.org>\r
+// The License.txt file describes the conditions under which this software may be distributed.\r
+\r
+#include <stdlib.h>\r
+#include <string.h>\r
+#include <ctype.h>\r
+#include <stdio.h>\r
+#include <stdarg.h>\r
+\r
+#include "Platform.h"\r
+\r
+#include "PropSet.h"\r
+#include "Accessor.h"\r
+#include "StyleContext.h"\r
+#include "KeyWords.h"\r
+#include "Scintilla.h"\r
+#include "SciLexer.h"\r
+\r
+#ifdef SCI_NAMESPACE\r
+using namespace Scintilla;\r
+#endif\r
+\r
+enum kwType { kwOther, kwClass, kwDef, kwImport };\r
+static const int indicatorWhitespace = 1;\r
+\r
+static bool IsPyComment(Accessor &styler, int pos, int len) {\r
+ return len > 0 && styler[pos] == '#';\r
+}\r
+\r
+static bool IsPyStringStart(int ch, int chNext, int chNext2) {\r
+ if (ch == '\'' || ch == '"')\r
+ return true;\r
+ if (ch == 'u' || ch == 'U') {\r
+ if (chNext == '"' || chNext == '\'')\r
+ return true;\r
+ if ((chNext == 'r' || chNext == 'R') && (chNext2 == '"' || chNext2 == '\''))\r
+ return true;\r
+ }\r
+ if ((ch == 'r' || ch == 'R') && (chNext == '"' || chNext == '\''))\r
+ return true;\r
+\r
+ return false;\r
+}\r
+\r
+/* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */\r
+static int GetPyStringState(Accessor &styler, int i, unsigned int *nextIndex) {\r
+ char ch = styler.SafeGetCharAt(i);\r
+ char chNext = styler.SafeGetCharAt(i + 1);\r
+\r
+ // Advance beyond r, u, or ur prefix, but bail if there are any unexpected chars\r
+ if (ch == 'r' || ch == 'R') {\r
+ i++;\r
+ ch = styler.SafeGetCharAt(i);\r
+ chNext = styler.SafeGetCharAt(i + 1);\r
+ } else if (ch == 'u' || ch == 'U') {\r
+ if (chNext == 'r' || chNext == 'R')\r
+ i += 2;\r
+ else\r
+ i += 1;\r
+ ch = styler.SafeGetCharAt(i);\r
+ chNext = styler.SafeGetCharAt(i + 1);\r
+ }\r
+\r
+ if (ch != '"' && ch != '\'') {\r
+ *nextIndex = i + 1;\r
+ return SCE_P_DEFAULT;\r
+ }\r
+\r
+ if (ch == chNext && ch == styler.SafeGetCharAt(i + 2)) {\r
+ *nextIndex = i + 3;\r
+\r
+ if (ch == '"')\r
+ return SCE_P_TRIPLEDOUBLE;\r
+ else\r
+ return SCE_P_TRIPLE;\r
+ } else {\r
+ *nextIndex = i + 1;\r
+\r
+ if (ch == '"')\r
+ return SCE_P_STRING;\r
+ else\r
+ return SCE_P_CHARACTER;\r
+ }\r
+}\r
+\r
+static inline bool IsAWordChar(int ch) {\r
+ return (ch < 0x80) && (isalnum(ch) || ch == '.' || ch == '_');\r
+}\r
+\r
+static inline bool IsAWordStart(int ch) {\r
+ return (ch < 0x80) && (isalnum(ch) || ch == '_');\r
+}\r
+\r
+static void ColourisePyDoc(unsigned int startPos, int length, int initStyle,\r
+ WordList *keywordlists[], Accessor &styler) {\r
+\r
+ int endPos = startPos + length;\r
+\r
+ // Backtrack to previous line in case need to fix its tab whinging\r
+ int lineCurrent = styler.GetLine(startPos);\r
+ if (startPos > 0) {\r
+ if (lineCurrent > 0) {\r
+ lineCurrent--;\r
+ startPos = styler.LineStart(lineCurrent);\r
+ if (startPos == 0)\r
+ initStyle = SCE_P_DEFAULT;\r
+ else\r
+ initStyle = styler.StyleAt(startPos - 1);\r
+ }\r
+ }\r
+\r
+ WordList &keywords = *keywordlists[0];\r
+ WordList &keywords2 = *keywordlists[1];\r
+\r
+ const int whingeLevel = styler.GetPropertyInt("tab.timmy.whinge.level");\r
+\r
+ initStyle = initStyle & 31;\r
+ if (initStyle == SCE_P_STRINGEOL) {\r
+ initStyle = SCE_P_DEFAULT;\r
+ }\r
+\r
+ kwType kwLast = kwOther;\r
+ int spaceFlags = 0;\r
+ styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);\r
+ bool hexadecimal = false;\r
+\r
+ StyleContext sc(startPos, endPos - startPos, initStyle, styler);\r
+\r
+ bool indentGood = true;\r
+ int startIndicator = sc.currentPos;\r
+\r
+ for (; sc.More(); sc.Forward()) {\r
+\r
+ if (sc.atLineStart) {\r
+ styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);\r
+ indentGood = true;\r
+ if (whingeLevel == 1) {\r
+ indentGood = (spaceFlags & wsInconsistent) == 0;\r
+ } else if (whingeLevel == 2) {\r
+ indentGood = (spaceFlags & wsSpaceTab) == 0;\r
+ } else if (whingeLevel == 3) {\r
+ indentGood = (spaceFlags & wsSpace) == 0;\r
+ } else if (whingeLevel == 4) {\r
+ indentGood = (spaceFlags & wsTab) == 0;\r
+ }\r
+ if (!indentGood) {\r
+ styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0);\r
+ startIndicator = sc.currentPos;\r
+ }\r
+ }\r
+\r
+ if (sc.atLineEnd) {\r
+ if ((sc.state == SCE_P_DEFAULT) ||\r
+ (sc.state == SCE_P_TRIPLE) ||\r
+ (sc.state == SCE_P_TRIPLEDOUBLE)) {\r
+ // Perform colourisation of white space and triple quoted strings at end of each line to allow\r
+ // tab marking to work inside white space and triple quoted strings\r
+ sc.SetState(sc.state);\r
+ }\r
+ lineCurrent++;\r
+ if ((sc.state == SCE_P_STRING) || (sc.state == SCE_P_CHARACTER)) {\r
+ sc.ChangeState(SCE_P_STRINGEOL);\r
+ sc.ForwardSetState(SCE_P_DEFAULT);\r
+ }\r
+ if (!sc.More())\r
+ break;\r
+ }\r
+\r
+ bool needEOLCheck = false;\r
+\r
+ // Check for a state end\r
+ if (sc.state == SCE_P_OPERATOR) {\r
+ kwLast = kwOther;\r
+ sc.SetState(SCE_P_DEFAULT);\r
+ } else if (sc.state == SCE_P_NUMBER) {\r
+ if (!IsAWordChar(sc.ch) &&\r
+ !(!hexadecimal && ((sc.ch == '+' || sc.ch == '-') && (sc.chPrev == 'e' || sc.chPrev == 'E')))) {\r
+ sc.SetState(SCE_P_DEFAULT);\r
+ }\r
+ } else if (sc.state == SCE_P_IDENTIFIER) {\r
+ if ((sc.ch == '.') || (!IsAWordChar(sc.ch))) {\r
+ char s[100];\r
+ sc.GetCurrent(s, sizeof(s));\r
+ int style = SCE_P_IDENTIFIER;\r
+ if ((kwLast == kwImport) && (strcmp(s, "as") == 0)) {\r
+ style = SCE_P_WORD;\r
+ } else if (keywords.InList(s)) {\r
+ style = SCE_P_WORD;\r
+ } else if (kwLast == kwClass) {\r
+ style = SCE_P_CLASSNAME;\r
+ } else if (kwLast == kwDef) {\r
+ style = SCE_P_DEFNAME;\r
+ } else if (keywords2.InList(s)) {\r
+ style = SCE_P_WORD2;\r
+ }\r
+ sc.ChangeState(style);\r
+ sc.SetState(SCE_P_DEFAULT);\r
+ if (style == SCE_P_WORD) {\r
+ if (0 == strcmp(s, "class"))\r
+ kwLast = kwClass;\r
+ else if (0 == strcmp(s, "def"))\r
+ kwLast = kwDef;\r
+ else if (0 == strcmp(s, "import"))\r
+ kwLast = kwImport;\r
+ else\r
+ kwLast = kwOther;\r
+ } else {\r
+ kwLast = kwOther;\r
+ }\r
+ }\r
+ } else if ((sc.state == SCE_P_COMMENTLINE) || (sc.state == SCE_P_COMMENTBLOCK)) {\r
+ if (sc.ch == '\r' || sc.ch == '\n') {\r
+ sc.SetState(SCE_P_DEFAULT);\r
+ }\r
+ } else if (sc.state == SCE_P_DECORATOR) {\r
+ if (!IsAWordChar(sc.ch)) {\r
+ sc.SetState(SCE_P_DEFAULT);\r
+ }\r
+ } else if ((sc.state == SCE_P_STRING) || (sc.state == SCE_P_CHARACTER)) {\r
+ if (sc.ch == '\\') {\r
+ if ((sc.chNext == '\r') && (sc.GetRelative(2) == '\n')) {\r
+ sc.Forward();\r
+ }\r
+ sc.Forward();\r
+ } else if ((sc.state == SCE_P_STRING) && (sc.ch == '\"')) {\r
+ sc.ForwardSetState(SCE_P_DEFAULT);\r
+ needEOLCheck = true;\r
+ } else if ((sc.state == SCE_P_CHARACTER) && (sc.ch == '\'')) {\r
+ sc.ForwardSetState(SCE_P_DEFAULT);\r
+ needEOLCheck = true;\r
+ }\r
+ } else if (sc.state == SCE_P_TRIPLE) {\r
+ if (sc.ch == '\\') {\r
+ sc.Forward();\r
+ } else if (sc.Match("\'\'\'")) {\r
+ sc.Forward();\r
+ sc.Forward();\r
+ sc.ForwardSetState(SCE_P_DEFAULT);\r
+ needEOLCheck = true;\r
+ }\r
+ } else if (sc.state == SCE_P_TRIPLEDOUBLE) {\r
+ if (sc.ch == '\\') {\r
+ sc.Forward();\r
+ } else if (sc.Match("\"\"\"")) {\r
+ sc.Forward();\r
+ sc.Forward();\r
+ sc.ForwardSetState(SCE_P_DEFAULT);\r
+ needEOLCheck = true;\r
+ }\r
+ }\r
+\r
+ if (!indentGood && !IsASpaceOrTab(sc.ch)) {\r
+ styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 1);\r
+ startIndicator = sc.currentPos;\r
+ indentGood = true;\r
+ }\r
+\r
+ // State exit code may have moved on to end of line\r
+ if (needEOLCheck && sc.atLineEnd) {\r
+ lineCurrent++;\r
+ styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);\r
+ if (!sc.More())\r
+ break;\r
+ }\r
+\r
+ // Check for a new state starting character\r
+ if (sc.state == SCE_P_DEFAULT) {\r
+ if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {\r
+ if (sc.ch == '0' && (sc.chNext == 'x' || sc.chNext == 'X')) {\r
+ hexadecimal = true;\r
+ } else {\r
+ hexadecimal = false;\r
+ }\r
+ sc.SetState(SCE_P_NUMBER);\r
+ } else if (isascii(sc.ch) && isoperator(static_cast<char>(sc.ch)) || sc.ch == '`') {\r
+ sc.SetState(SCE_P_OPERATOR);\r
+ } else if (sc.ch == '#') {\r
+ sc.SetState(sc.chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE);\r
+ } else if (sc.ch == '@') {\r
+ sc.SetState(SCE_P_DECORATOR);\r
+ } else if (IsPyStringStart(sc.ch, sc.chNext, sc.GetRelative(2))) {\r
+ unsigned int nextIndex = 0;\r
+ sc.SetState(GetPyStringState(styler, sc.currentPos, &nextIndex));\r
+ while (nextIndex > (sc.currentPos + 1) && sc.More()) {\r
+ sc.Forward();\r
+ }\r
+ } else if (IsAWordStart(sc.ch)) {\r
+ sc.SetState(SCE_P_IDENTIFIER);\r
+ }\r
+ }\r
+ }\r
+ styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0);\r
+ sc.Complete();\r
+}\r
+\r
+static bool IsCommentLine(int line, Accessor &styler) {\r
+ int pos = styler.LineStart(line);\r
+ int eol_pos = styler.LineStart(line + 1) - 1;\r
+ for (int i = pos; i < eol_pos; i++) {\r
+ char ch = styler[i];\r
+ if (ch == '#')\r
+ return true;\r
+ else if (ch != ' ' && ch != '\t')\r
+ return false;\r
+ }\r
+ return false;\r
+}\r
+\r
+static bool IsQuoteLine(int line, Accessor &styler) {\r
+ int style = styler.StyleAt(styler.LineStart(line)) & 31;\r
+ return ((style == SCE_P_TRIPLE) || (style == SCE_P_TRIPLEDOUBLE));\r
+}\r
+\r
+\r
+static void FoldPyDoc(unsigned int startPos, int length, int /*initStyle - unused*/,\r
+ WordList *[], Accessor &styler) {\r
+ const int maxPos = startPos + length;\r
+ const int maxLines = styler.GetLine(maxPos - 1); // Requested last line\r
+ const int docLines = styler.GetLine(styler.Length() - 1); // Available last line\r
+ const bool foldComment = styler.GetPropertyInt("fold.comment.python") != 0;\r
+ const bool foldQuotes = styler.GetPropertyInt("fold.quotes.python") != 0;\r
+\r
+ // Backtrack to previous non-blank line so we can determine indent level\r
+ // for any white space lines (needed esp. within triple quoted strings)\r
+ // and so we can fix any preceding fold level (which is why we go back\r
+ // at least one line in all cases)\r
+ int spaceFlags = 0;\r
+ int lineCurrent = styler.GetLine(startPos);\r
+ int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);\r
+ while (lineCurrent > 0) {\r
+ lineCurrent--;\r
+ indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);\r
+ if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG) &&\r
+ (!IsCommentLine(lineCurrent, styler)) &&\r
+ (!IsQuoteLine(lineCurrent, styler)))\r
+ break;\r
+ }\r
+ int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;\r
+\r
+ // Set up initial loop state\r
+ startPos = styler.LineStart(lineCurrent);\r
+ int prev_state = SCE_P_DEFAULT & 31;\r
+ if (lineCurrent >= 1)\r
+ prev_state = styler.StyleAt(startPos - 1) & 31;\r
+ int prevQuote = foldQuotes && ((prev_state == SCE_P_TRIPLE) || (prev_state == SCE_P_TRIPLEDOUBLE));\r
+ int prevComment = 0;\r
+ if (lineCurrent >= 1)\r
+ prevComment = foldComment && IsCommentLine(lineCurrent - 1, styler);\r
+\r
+ // Process all characters to end of requested range or end of any triple quote\r
+ // or comment that hangs over the end of the range. Cap processing in all cases\r
+ // to end of document (in case of unclosed quote or comment at end).\r
+ while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevQuote || prevComment)) {\r
+\r
+ // Gather info\r
+ int lev = indentCurrent;\r
+ int lineNext = lineCurrent + 1;\r
+ int indentNext = indentCurrent;\r
+ int quote = false;\r
+ if (lineNext <= docLines) {\r
+ // Information about next line is only available if not at end of document\r
+ indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);\r
+ int style = styler.StyleAt(styler.LineStart(lineNext)) & 31;\r
+ quote = foldQuotes && ((style == SCE_P_TRIPLE) || (style == SCE_P_TRIPLEDOUBLE));\r
+ }\r
+ const int quote_start = (quote && !prevQuote);\r
+ const int quote_continue = (quote && prevQuote);\r
+ const int comment = foldComment && IsCommentLine(lineCurrent, styler);\r
+ const int comment_start = (comment && !prevComment && (lineNext <= docLines) &&\r
+ IsCommentLine(lineNext, styler) && (lev > SC_FOLDLEVELBASE));\r
+ const int comment_continue = (comment && prevComment);\r
+ if ((!quote || !prevQuote) && !comment)\r
+ indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;\r
+ if (quote)\r
+ indentNext = indentCurrentLevel;\r
+ if (indentNext & SC_FOLDLEVELWHITEFLAG)\r
+ indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;\r
+\r
+ if (quote_start) {\r
+ // Place fold point at start of triple quoted string\r
+ lev |= SC_FOLDLEVELHEADERFLAG;\r
+ } else if (quote_continue || prevQuote) {\r
+ // Add level to rest of lines in the string\r
+ lev = lev + 1;\r
+ } else if (comment_start) {\r
+ // Place fold point at start of a block of comments\r
+ lev |= SC_FOLDLEVELHEADERFLAG;\r
+ } else if (comment_continue) {\r
+ // Add level to rest of lines in the block\r
+ lev = lev + 1;\r
+ }\r
+\r
+ // Skip past any blank lines for next indent level info; we skip also\r
+ // comments (all comments, not just those starting in column 0)\r
+ // which effectively folds them into surrounding code rather\r
+ // than screwing up folding.\r
+\r
+ while (!quote &&\r
+ (lineNext < docLines) &&\r
+ ((indentNext & SC_FOLDLEVELWHITEFLAG) ||\r
+ (lineNext <= docLines && IsCommentLine(lineNext, styler)))) {\r
+\r
+ lineNext++;\r
+ indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);\r
+ }\r
+\r
+ const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK;\r
+ const int levelBeforeComments = Platform::Maximum(indentCurrentLevel,levelAfterComments);\r
+\r
+ // Now set all the indent levels on the lines we skipped\r
+ // Do this from end to start. Once we encounter one line\r
+ // which is indented more than the line after the end of\r
+ // the comment-block, use the level of the block before\r
+\r
+ int skipLine = lineNext;\r
+ int skipLevel = levelAfterComments;\r
+\r
+ while (--skipLine > lineCurrent) {\r
+ int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL);\r
+\r
+ if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)\r
+ skipLevel = levelBeforeComments;\r
+\r
+ int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;\r
+\r
+ styler.SetLevel(skipLine, skipLevel | whiteFlag);\r
+ }\r
+\r
+ // Set fold header on non-quote/non-comment line\r
+ if (!quote && !comment && !(indentCurrent & SC_FOLDLEVELWHITEFLAG) ) {\r
+ if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK))\r
+ lev |= SC_FOLDLEVELHEADERFLAG;\r
+ }\r
+\r
+ // Keep track of triple quote and block comment state of previous line\r
+ prevQuote = quote;\r
+ prevComment = comment_start || comment_continue;\r
+\r
+ // Set fold level for this line and move to next line\r
+ styler.SetLevel(lineCurrent, lev);\r
+ indentCurrent = indentNext;\r
+ lineCurrent = lineNext;\r
+ }\r
+\r
+ // NOTE: Cannot set level of last line here because indentCurrent doesn't have\r
+ // header flag set; the loop above is crafted to take care of this case!\r
+ //styler.SetLevel(lineCurrent, indentCurrent);\r
+}\r
+\r
+static const char * const pythonWordListDesc[] = {\r
+ "Keywords",\r
+ "Highlighted identifiers",\r
+ 0\r
+};\r
+\r
+LexerModule lmPython(SCLEX_PYTHON, ColourisePyDoc, "python", FoldPyDoc,\r
+ pythonWordListDesc);\r