LexPython.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  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.
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <stdio.h>
  10. #include <stdarg.h>
  11. #include <assert.h>
  12. #include <ctype.h>
  13. #include <string>
  14. #include <vector>
  15. #include <map>
  16. #include "ILexer.h"
  17. #include "Scintilla.h"
  18. #include "SciLexer.h"
  19. #include "WordList.h"
  20. #include "LexAccessor.h"
  21. #include "Accessor.h"
  22. #include "StyleContext.h"
  23. #include "CharacterSet.h"
  24. #include "LexerModule.h"
  25. #include "OptionSet.h"
  26. #include "SubStyles.h"
  27. #ifdef SCI_NAMESPACE
  28. using namespace Scintilla;
  29. #endif
  30. namespace {
  31. // Use an unnamed namespace to protect the functions and classes from name conflicts
  32. /* kwCDef, kwCTypeName only used for Cython */
  33. enum kwType { kwOther, kwClass, kwDef, kwImport, kwCDef, kwCTypeName, kwCPDef };
  34. enum literalsAllowed { litNone = 0, litU = 1, litB = 2 };
  35. const int indicatorWhitespace = 1;
  36. bool IsPyComment(Accessor &styler, Sci_Position pos, Sci_Position len) {
  37. return len > 0 && styler[pos] == '#';
  38. }
  39. bool IsPyStringTypeChar(int ch, literalsAllowed allowed) {
  40. return
  41. ((allowed & litB) && (ch == 'b' || ch == 'B')) ||
  42. ((allowed & litU) && (ch == 'u' || ch == 'U'));
  43. }
  44. bool IsPyStringStart(int ch, int chNext, int chNext2, literalsAllowed allowed) {
  45. if (ch == '\'' || ch == '"')
  46. return true;
  47. if (IsPyStringTypeChar(ch, allowed)) {
  48. if (chNext == '"' || chNext == '\'')
  49. return true;
  50. if ((chNext == 'r' || chNext == 'R') && (chNext2 == '"' || chNext2 == '\''))
  51. return true;
  52. }
  53. if ((ch == 'r' || ch == 'R') && (chNext == '"' || chNext == '\''))
  54. return true;
  55. return false;
  56. }
  57. /* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */
  58. int GetPyStringState(Accessor &styler, Sci_Position i, Sci_PositionU *nextIndex, literalsAllowed allowed) {
  59. char ch = styler.SafeGetCharAt(i);
  60. char chNext = styler.SafeGetCharAt(i + 1);
  61. // Advance beyond r, u, or ur prefix (or r, b, or br in Python 3.0), but bail if there are any unexpected chars
  62. if (ch == 'r' || ch == 'R') {
  63. i++;
  64. ch = styler.SafeGetCharAt(i);
  65. chNext = styler.SafeGetCharAt(i + 1);
  66. } else if (IsPyStringTypeChar(ch, allowed)) {
  67. if (chNext == 'r' || chNext == 'R')
  68. i += 2;
  69. else
  70. i += 1;
  71. ch = styler.SafeGetCharAt(i);
  72. chNext = styler.SafeGetCharAt(i + 1);
  73. }
  74. if (ch != '"' && ch != '\'') {
  75. *nextIndex = i + 1;
  76. return SCE_P_DEFAULT;
  77. }
  78. if (ch == chNext && ch == styler.SafeGetCharAt(i + 2)) {
  79. *nextIndex = i + 3;
  80. if (ch == '"')
  81. return SCE_P_TRIPLEDOUBLE;
  82. else
  83. return SCE_P_TRIPLE;
  84. } else {
  85. *nextIndex = i + 1;
  86. if (ch == '"')
  87. return SCE_P_STRING;
  88. else
  89. return SCE_P_CHARACTER;
  90. }
  91. }
  92. inline bool IsAWordChar(int ch) {
  93. return (ch < 0x80) && (isalnum(ch) || ch == '.' || ch == '_');
  94. }
  95. inline bool IsAWordStart(int ch) {
  96. return (ch < 0x80) && (isalnum(ch) || ch == '_');
  97. }
  98. static bool IsFirstNonWhitespace(Sci_Position pos, Accessor &styler) {
  99. Sci_Position line = styler.GetLine(pos);
  100. Sci_Position start_pos = styler.LineStart(line);
  101. for (Sci_Position i = start_pos; i < pos; i++) {
  102. char ch = styler[i];
  103. if (!(ch == ' ' || ch == '\t'))
  104. return false;
  105. }
  106. return true;
  107. }
  108. // Options used for LexerPython
  109. struct OptionsPython {
  110. int whingeLevel;
  111. bool base2or8Literals;
  112. bool stringsU;
  113. bool stringsB;
  114. bool stringsOverNewline;
  115. bool keywords2NoSubIdentifiers;
  116. bool fold;
  117. bool foldQuotes;
  118. bool foldCompact;
  119. OptionsPython() {
  120. whingeLevel = 0;
  121. base2or8Literals = true;
  122. stringsU = true;
  123. stringsB = true;
  124. stringsOverNewline = false;
  125. keywords2NoSubIdentifiers = false;
  126. fold = false;
  127. foldQuotes = false;
  128. foldCompact = false;
  129. }
  130. literalsAllowed AllowedLiterals() const {
  131. literalsAllowed allowedLiterals = stringsU ? litU : litNone;
  132. if (stringsB)
  133. allowedLiterals = static_cast<literalsAllowed>(allowedLiterals | litB);
  134. return allowedLiterals;
  135. }
  136. };
  137. static const char *const pythonWordListDesc[] = {
  138. "Keywords",
  139. "Highlighted identifiers",
  140. 0
  141. };
  142. struct OptionSetPython : public OptionSet<OptionsPython> {
  143. OptionSetPython() {
  144. DefineProperty("tab.timmy.whinge.level", &OptionsPython::whingeLevel,
  145. "For Python code, checks whether indenting is consistent. "
  146. "The default, 0 turns off indentation checking, "
  147. "1 checks whether each line is potentially inconsistent with the previous line, "
  148. "2 checks whether any space characters occur before a tab character in the indentation, "
  149. "3 checks whether any spaces are in the indentation, and "
  150. "4 checks for any tab characters in the indentation. "
  151. "1 is a good level to use.");
  152. DefineProperty("lexer.python.literals.binary", &OptionsPython::base2or8Literals,
  153. "Set to 0 to not recognise Python 3 binary and octal literals: 0b1011 0o712.");
  154. DefineProperty("lexer.python.strings.u", &OptionsPython::stringsU,
  155. "Set to 0 to not recognise Python Unicode literals u\"x\" as used before Python 3.");
  156. DefineProperty("lexer.python.strings.b", &OptionsPython::stringsB,
  157. "Set to 0 to not recognise Python 3 bytes literals b\"x\".");
  158. DefineProperty("lexer.python.strings.over.newline", &OptionsPython::stringsOverNewline,
  159. "Set to 1 to allow strings to span newline characters.");
  160. DefineProperty("lexer.python.keywords2.no.sub.identifiers", &OptionsPython::keywords2NoSubIdentifiers,
  161. "When enabled, it will not style keywords2 items that are used as a sub-identifier. "
  162. "Example: when set, will not highlight \"foo.open\" when \"open\" is a keywords2 item.");
  163. DefineProperty("fold", &OptionsPython::fold);
  164. DefineProperty("fold.quotes.python", &OptionsPython::foldQuotes,
  165. "This option enables folding multi-line quoted strings when using the Python lexer.");
  166. DefineProperty("fold.compact", &OptionsPython::foldCompact);
  167. DefineWordListSets(pythonWordListDesc);
  168. }
  169. };
  170. const char styleSubable[] = { SCE_P_IDENTIFIER, 0 };
  171. }
  172. class LexerPython : public ILexerWithSubStyles {
  173. WordList keywords;
  174. WordList keywords2;
  175. OptionsPython options;
  176. OptionSetPython osPython;
  177. enum { ssIdentifier };
  178. SubStyles subStyles;
  179. public:
  180. explicit LexerPython() :
  181. subStyles(styleSubable, 0x80, 0x40, 0) {
  182. }
  183. virtual ~LexerPython() {
  184. }
  185. void SCI_METHOD Release() {
  186. delete this;
  187. }
  188. int SCI_METHOD Version() const {
  189. return lvSubStyles;
  190. }
  191. const char * SCI_METHOD PropertyNames() {
  192. return osPython.PropertyNames();
  193. }
  194. int SCI_METHOD PropertyType(const char *name) {
  195. return osPython.PropertyType(name);
  196. }
  197. const char * SCI_METHOD DescribeProperty(const char *name) {
  198. return osPython.DescribeProperty(name);
  199. }
  200. Sci_Position SCI_METHOD PropertySet(const char *key, const char *val);
  201. const char * SCI_METHOD DescribeWordListSets() {
  202. return osPython.DescribeWordListSets();
  203. }
  204. Sci_Position SCI_METHOD WordListSet(int n, const char *wl);
  205. void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess);
  206. void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess);
  207. void * SCI_METHOD PrivateCall(int, void *) {
  208. return 0;
  209. }
  210. int SCI_METHOD LineEndTypesSupported() {
  211. return SC_LINE_END_TYPE_UNICODE;
  212. }
  213. int SCI_METHOD AllocateSubStyles(int styleBase, int numberStyles) {
  214. return subStyles.Allocate(styleBase, numberStyles);
  215. }
  216. int SCI_METHOD SubStylesStart(int styleBase) {
  217. return subStyles.Start(styleBase);
  218. }
  219. int SCI_METHOD SubStylesLength(int styleBase) {
  220. return subStyles.Length(styleBase);
  221. }
  222. int SCI_METHOD StyleFromSubStyle(int subStyle) {
  223. int styleBase = subStyles.BaseStyle(subStyle);
  224. return styleBase;
  225. }
  226. int SCI_METHOD PrimaryStyleFromStyle(int style) {
  227. return style;
  228. }
  229. void SCI_METHOD FreeSubStyles() {
  230. subStyles.Free();
  231. }
  232. void SCI_METHOD SetIdentifiers(int style, const char *identifiers) {
  233. subStyles.SetIdentifiers(style, identifiers);
  234. }
  235. int SCI_METHOD DistanceToSecondaryStyles() {
  236. return 0;
  237. }
  238. const char * SCI_METHOD GetSubStyleBases() {
  239. return styleSubable;
  240. }
  241. static ILexer *LexerFactoryPython() {
  242. return new LexerPython();
  243. }
  244. };
  245. Sci_Position SCI_METHOD LexerPython::PropertySet(const char *key, const char *val) {
  246. if (osPython.PropertySet(&options, key, val)) {
  247. return 0;
  248. }
  249. return -1;
  250. }
  251. Sci_Position SCI_METHOD LexerPython::WordListSet(int n, const char *wl) {
  252. WordList *wordListN = 0;
  253. switch (n) {
  254. case 0:
  255. wordListN = &keywords;
  256. break;
  257. case 1:
  258. wordListN = &keywords2;
  259. break;
  260. }
  261. Sci_Position firstModification = -1;
  262. if (wordListN) {
  263. WordList wlNew;
  264. wlNew.Set(wl);
  265. if (*wordListN != wlNew) {
  266. wordListN->Set(wl);
  267. firstModification = 0;
  268. }
  269. }
  270. return firstModification;
  271. }
  272. void SCI_METHOD LexerPython::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) {
  273. Accessor styler(pAccess, NULL);
  274. const Sci_Position endPos = startPos + length;
  275. // Backtrack to previous line in case need to fix its tab whinging
  276. Sci_Position lineCurrent = styler.GetLine(startPos);
  277. if (startPos > 0) {
  278. if (lineCurrent > 0) {
  279. lineCurrent--;
  280. // Look for backslash-continued lines
  281. while (lineCurrent > 0) {
  282. Sci_Position eolPos = styler.LineStart(lineCurrent) - 1;
  283. int eolStyle = styler.StyleAt(eolPos);
  284. if (eolStyle == SCE_P_STRING
  285. || eolStyle == SCE_P_CHARACTER
  286. || eolStyle == SCE_P_STRINGEOL) {
  287. lineCurrent -= 1;
  288. } else {
  289. break;
  290. }
  291. }
  292. startPos = styler.LineStart(lineCurrent);
  293. }
  294. initStyle = startPos == 0 ? SCE_P_DEFAULT : styler.StyleAt(startPos - 1);
  295. }
  296. const literalsAllowed allowedLiterals = options.AllowedLiterals();
  297. initStyle = initStyle & 31;
  298. if (initStyle == SCE_P_STRINGEOL) {
  299. initStyle = SCE_P_DEFAULT;
  300. }
  301. kwType kwLast = kwOther;
  302. int spaceFlags = 0;
  303. styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);
  304. bool base_n_number = false;
  305. const WordClassifier &classifierIdentifiers = subStyles.Classifier(SCE_P_IDENTIFIER);
  306. StyleContext sc(startPos, endPos - startPos, initStyle, styler);
  307. bool indentGood = true;
  308. Sci_Position startIndicator = sc.currentPos;
  309. bool inContinuedString = false;
  310. for (; sc.More(); sc.Forward()) {
  311. if (sc.atLineStart) {
  312. styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);
  313. indentGood = true;
  314. if (options.whingeLevel == 1) {
  315. indentGood = (spaceFlags & wsInconsistent) == 0;
  316. } else if (options.whingeLevel == 2) {
  317. indentGood = (spaceFlags & wsSpaceTab) == 0;
  318. } else if (options.whingeLevel == 3) {
  319. indentGood = (spaceFlags & wsSpace) == 0;
  320. } else if (options.whingeLevel == 4) {
  321. indentGood = (spaceFlags & wsTab) == 0;
  322. }
  323. if (!indentGood) {
  324. styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0);
  325. startIndicator = sc.currentPos;
  326. }
  327. }
  328. if (sc.atLineEnd) {
  329. if ((sc.state == SCE_P_DEFAULT) ||
  330. (sc.state == SCE_P_TRIPLE) ||
  331. (sc.state == SCE_P_TRIPLEDOUBLE)) {
  332. // Perform colourisation of white space and triple quoted strings at end of each line to allow
  333. // tab marking to work inside white space and triple quoted strings
  334. sc.SetState(sc.state);
  335. }
  336. lineCurrent++;
  337. if ((sc.state == SCE_P_STRING) || (sc.state == SCE_P_CHARACTER)) {
  338. if (inContinuedString || options.stringsOverNewline) {
  339. inContinuedString = false;
  340. } else {
  341. sc.ChangeState(SCE_P_STRINGEOL);
  342. sc.ForwardSetState(SCE_P_DEFAULT);
  343. }
  344. }
  345. if (!sc.More())
  346. break;
  347. }
  348. bool needEOLCheck = false;
  349. // Check for a state end
  350. if (sc.state == SCE_P_OPERATOR) {
  351. kwLast = kwOther;
  352. sc.SetState(SCE_P_DEFAULT);
  353. } else if (sc.state == SCE_P_NUMBER) {
  354. if (!IsAWordChar(sc.ch) &&
  355. !(!base_n_number && ((sc.ch == '+' || sc.ch == '-') && (sc.chPrev == 'e' || sc.chPrev == 'E')))) {
  356. sc.SetState(SCE_P_DEFAULT);
  357. }
  358. } else if (sc.state == SCE_P_IDENTIFIER) {
  359. if ((sc.ch == '.') || (!IsAWordChar(sc.ch))) {
  360. char s[100];
  361. sc.GetCurrent(s, sizeof(s));
  362. int style = SCE_P_IDENTIFIER;
  363. if ((kwLast == kwImport) && (strcmp(s, "as") == 0)) {
  364. style = SCE_P_WORD;
  365. } else if (keywords.InList(s)) {
  366. style = SCE_P_WORD;
  367. } else if (kwLast == kwClass) {
  368. style = SCE_P_CLASSNAME;
  369. } else if (kwLast == kwDef) {
  370. style = SCE_P_DEFNAME;
  371. } else if (kwLast == kwCDef || kwLast == kwCPDef) {
  372. Sci_Position pos = sc.currentPos;
  373. unsigned char ch = styler.SafeGetCharAt(pos, '\0');
  374. while (ch != '\0') {
  375. if (ch == '(') {
  376. style = SCE_P_DEFNAME;
  377. break;
  378. } else if (ch == ':') {
  379. style = SCE_P_CLASSNAME;
  380. break;
  381. } else if (ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r') {
  382. pos++;
  383. ch = styler.SafeGetCharAt(pos, '\0');
  384. } else {
  385. break;
  386. }
  387. }
  388. } else if (keywords2.InList(s)) {
  389. if (options.keywords2NoSubIdentifiers) {
  390. // We don't want to highlight keywords2
  391. // that are used as a sub-identifier,
  392. // i.e. not open in "foo.open".
  393. Sci_Position pos = styler.GetStartSegment() - 1;
  394. if (pos < 0 || (styler.SafeGetCharAt(pos, '\0') != '.'))
  395. style = SCE_P_WORD2;
  396. } else {
  397. style = SCE_P_WORD2;
  398. }
  399. } else {
  400. int subStyle = classifierIdentifiers.ValueFor(s);
  401. if (subStyle >= 0) {
  402. style = subStyle;
  403. }
  404. }
  405. sc.ChangeState(style);
  406. sc.SetState(SCE_P_DEFAULT);
  407. if (style == SCE_P_WORD) {
  408. if (0 == strcmp(s, "class"))
  409. kwLast = kwClass;
  410. else if (0 == strcmp(s, "def"))
  411. kwLast = kwDef;
  412. else if (0 == strcmp(s, "import"))
  413. kwLast = kwImport;
  414. else if (0 == strcmp(s, "cdef"))
  415. kwLast = kwCDef;
  416. else if (0 == strcmp(s, "cpdef"))
  417. kwLast = kwCPDef;
  418. else if (0 == strcmp(s, "cimport"))
  419. kwLast = kwImport;
  420. else if (kwLast != kwCDef && kwLast != kwCPDef)
  421. kwLast = kwOther;
  422. } else if (kwLast != kwCDef && kwLast != kwCPDef) {
  423. kwLast = kwOther;
  424. }
  425. }
  426. } else if ((sc.state == SCE_P_COMMENTLINE) || (sc.state == SCE_P_COMMENTBLOCK)) {
  427. if (sc.ch == '\r' || sc.ch == '\n') {
  428. sc.SetState(SCE_P_DEFAULT);
  429. }
  430. } else if (sc.state == SCE_P_DECORATOR) {
  431. if (!IsAWordChar(sc.ch)) {
  432. sc.SetState(SCE_P_DEFAULT);
  433. }
  434. } else if ((sc.state == SCE_P_STRING) || (sc.state == SCE_P_CHARACTER)) {
  435. if (sc.ch == '\\') {
  436. if ((sc.chNext == '\r') && (sc.GetRelative(2) == '\n')) {
  437. sc.Forward();
  438. }
  439. if (sc.chNext == '\n' || sc.chNext == '\r') {
  440. inContinuedString = true;
  441. } else {
  442. // Don't roll over the newline.
  443. sc.Forward();
  444. }
  445. } else if ((sc.state == SCE_P_STRING) && (sc.ch == '\"')) {
  446. sc.ForwardSetState(SCE_P_DEFAULT);
  447. needEOLCheck = true;
  448. } else if ((sc.state == SCE_P_CHARACTER) && (sc.ch == '\'')) {
  449. sc.ForwardSetState(SCE_P_DEFAULT);
  450. needEOLCheck = true;
  451. }
  452. } else if (sc.state == SCE_P_TRIPLE) {
  453. if (sc.ch == '\\') {
  454. sc.Forward();
  455. } else if (sc.Match("\'\'\'")) {
  456. sc.Forward();
  457. sc.Forward();
  458. sc.ForwardSetState(SCE_P_DEFAULT);
  459. needEOLCheck = true;
  460. }
  461. } else if (sc.state == SCE_P_TRIPLEDOUBLE) {
  462. if (sc.ch == '\\') {
  463. sc.Forward();
  464. } else if (sc.Match("\"\"\"")) {
  465. sc.Forward();
  466. sc.Forward();
  467. sc.ForwardSetState(SCE_P_DEFAULT);
  468. needEOLCheck = true;
  469. }
  470. }
  471. if (!indentGood && !IsASpaceOrTab(sc.ch)) {
  472. styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 1);
  473. startIndicator = sc.currentPos;
  474. indentGood = true;
  475. }
  476. // One cdef or cpdef line, clear kwLast only at end of line
  477. if ((kwLast == kwCDef || kwLast == kwCPDef) && sc.atLineEnd) {
  478. kwLast = kwOther;
  479. }
  480. // State exit code may have moved on to end of line
  481. if (needEOLCheck && sc.atLineEnd) {
  482. lineCurrent++;
  483. styler.IndentAmount(lineCurrent, &spaceFlags, IsPyComment);
  484. if (!sc.More())
  485. break;
  486. }
  487. // Check for a new state starting character
  488. if (sc.state == SCE_P_DEFAULT) {
  489. if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
  490. if (sc.ch == '0' && (sc.chNext == 'x' || sc.chNext == 'X')) {
  491. base_n_number = true;
  492. sc.SetState(SCE_P_NUMBER);
  493. } else if (sc.ch == '0' &&
  494. (sc.chNext == 'o' || sc.chNext == 'O' || sc.chNext == 'b' || sc.chNext == 'B')) {
  495. if (options.base2or8Literals) {
  496. base_n_number = true;
  497. sc.SetState(SCE_P_NUMBER);
  498. } else {
  499. sc.SetState(SCE_P_NUMBER);
  500. sc.ForwardSetState(SCE_P_IDENTIFIER);
  501. }
  502. } else {
  503. base_n_number = false;
  504. sc.SetState(SCE_P_NUMBER);
  505. }
  506. } else if ((IsASCII(sc.ch) && isoperator(static_cast<char>(sc.ch))) || sc.ch == '`') {
  507. sc.SetState(SCE_P_OPERATOR);
  508. } else if (sc.ch == '#') {
  509. sc.SetState(sc.chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE);
  510. } else if (sc.ch == '@') {
  511. if (IsFirstNonWhitespace(sc.currentPos, styler))
  512. sc.SetState(SCE_P_DECORATOR);
  513. else
  514. sc.SetState(SCE_P_OPERATOR);
  515. } else if (IsPyStringStart(sc.ch, sc.chNext, sc.GetRelative(2), allowedLiterals)) {
  516. Sci_PositionU nextIndex = 0;
  517. sc.SetState(GetPyStringState(styler, sc.currentPos, &nextIndex, allowedLiterals));
  518. while (nextIndex > (sc.currentPos + 1) && sc.More()) {
  519. sc.Forward();
  520. }
  521. } else if (IsAWordStart(sc.ch)) {
  522. sc.SetState(SCE_P_IDENTIFIER);
  523. }
  524. }
  525. }
  526. styler.IndicatorFill(startIndicator, sc.currentPos, indicatorWhitespace, 0);
  527. sc.Complete();
  528. }
  529. static bool IsCommentLine(Sci_Position line, Accessor &styler) {
  530. Sci_Position pos = styler.LineStart(line);
  531. Sci_Position eol_pos = styler.LineStart(line + 1) - 1;
  532. for (Sci_Position i = pos; i < eol_pos; i++) {
  533. char ch = styler[i];
  534. if (ch == '#')
  535. return true;
  536. else if (ch != ' ' && ch != '\t')
  537. return false;
  538. }
  539. return false;
  540. }
  541. static bool IsQuoteLine(Sci_Position line, Accessor &styler) {
  542. int style = styler.StyleAt(styler.LineStart(line)) & 31;
  543. return ((style == SCE_P_TRIPLE) || (style == SCE_P_TRIPLEDOUBLE));
  544. }
  545. void SCI_METHOD LexerPython::Fold(Sci_PositionU startPos, Sci_Position length, int /*initStyle - unused*/, IDocument *pAccess) {
  546. if (!options.fold)
  547. return;
  548. Accessor styler(pAccess, NULL);
  549. const Sci_Position maxPos = startPos + length;
  550. const Sci_Position maxLines = (maxPos == styler.Length()) ? styler.GetLine(maxPos) : styler.GetLine(maxPos - 1); // Requested last line
  551. const Sci_Position docLines = styler.GetLine(styler.Length()); // Available last line
  552. // Backtrack to previous non-blank line so we can determine indent level
  553. // for any white space lines (needed esp. within triple quoted strings)
  554. // and so we can fix any preceding fold level (which is why we go back
  555. // at least one line in all cases)
  556. int spaceFlags = 0;
  557. Sci_Position lineCurrent = styler.GetLine(startPos);
  558. int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
  559. while (lineCurrent > 0) {
  560. lineCurrent--;
  561. indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
  562. if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG) &&
  563. (!IsCommentLine(lineCurrent, styler)) &&
  564. (!IsQuoteLine(lineCurrent, styler)))
  565. break;
  566. }
  567. int indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
  568. // Set up initial loop state
  569. startPos = styler.LineStart(lineCurrent);
  570. int prev_state = SCE_P_DEFAULT & 31;
  571. if (lineCurrent >= 1)
  572. prev_state = styler.StyleAt(startPos - 1) & 31;
  573. int prevQuote = options.foldQuotes && ((prev_state == SCE_P_TRIPLE) || (prev_state == SCE_P_TRIPLEDOUBLE));
  574. // Process all characters to end of requested range or end of any triple quote
  575. //that hangs over the end of the range. Cap processing in all cases
  576. // to end of document (in case of unclosed quote at end).
  577. while ((lineCurrent <= docLines) && ((lineCurrent <= maxLines) || prevQuote)) {
  578. // Gather info
  579. int lev = indentCurrent;
  580. Sci_Position lineNext = lineCurrent + 1;
  581. int indentNext = indentCurrent;
  582. int quote = false;
  583. if (lineNext <= docLines) {
  584. // Information about next line is only available if not at end of document
  585. indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
  586. Sci_Position lookAtPos = (styler.LineStart(lineNext) == styler.Length()) ? styler.Length() - 1 : styler.LineStart(lineNext);
  587. int style = styler.StyleAt(lookAtPos) & 31;
  588. quote = options.foldQuotes && ((style == SCE_P_TRIPLE) || (style == SCE_P_TRIPLEDOUBLE));
  589. }
  590. const int quote_start = (quote && !prevQuote);
  591. const int quote_continue = (quote && prevQuote);
  592. if (!quote || !prevQuote)
  593. indentCurrentLevel = indentCurrent & SC_FOLDLEVELNUMBERMASK;
  594. if (quote)
  595. indentNext = indentCurrentLevel;
  596. if (indentNext & SC_FOLDLEVELWHITEFLAG)
  597. indentNext = SC_FOLDLEVELWHITEFLAG | indentCurrentLevel;
  598. if (quote_start) {
  599. // Place fold point at start of triple quoted string
  600. lev |= SC_FOLDLEVELHEADERFLAG;
  601. } else if (quote_continue || prevQuote) {
  602. // Add level to rest of lines in the string
  603. lev = lev + 1;
  604. }
  605. // Skip past any blank lines for next indent level info; we skip also
  606. // comments (all comments, not just those starting in column 0)
  607. // which effectively folds them into surrounding code rather
  608. // than screwing up folding.
  609. while (!quote &&
  610. (lineNext < docLines) &&
  611. ((indentNext & SC_FOLDLEVELWHITEFLAG) ||
  612. (lineNext <= docLines && IsCommentLine(lineNext, styler)))) {
  613. lineNext++;
  614. indentNext = styler.IndentAmount(lineNext, &spaceFlags, NULL);
  615. }
  616. const int levelAfterComments = indentNext & SC_FOLDLEVELNUMBERMASK;
  617. const int levelBeforeComments = Maximum(indentCurrentLevel,levelAfterComments);
  618. // Now set all the indent levels on the lines we skipped
  619. // Do this from end to start. Once we encounter one line
  620. // which is indented more than the line after the end of
  621. // the comment-block, use the level of the block before
  622. Sci_Position skipLine = lineNext;
  623. int skipLevel = levelAfterComments;
  624. while (--skipLine > lineCurrent) {
  625. int skipLineIndent = styler.IndentAmount(skipLine, &spaceFlags, NULL);
  626. if (options.foldCompact) {
  627. if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments)
  628. skipLevel = levelBeforeComments;
  629. int whiteFlag = skipLineIndent & SC_FOLDLEVELWHITEFLAG;
  630. styler.SetLevel(skipLine, skipLevel | whiteFlag);
  631. } else {
  632. if ((skipLineIndent & SC_FOLDLEVELNUMBERMASK) > levelAfterComments &&
  633. !(skipLineIndent & SC_FOLDLEVELWHITEFLAG) &&
  634. !IsCommentLine(skipLine, styler))
  635. skipLevel = levelBeforeComments;
  636. styler.SetLevel(skipLine, skipLevel);
  637. }
  638. }
  639. // Set fold header on non-quote line
  640. if (!quote && !(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
  641. if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK))
  642. lev |= SC_FOLDLEVELHEADERFLAG;
  643. }
  644. // Keep track of triple quote state of previous line
  645. prevQuote = quote;
  646. // Set fold level for this line and move to next line
  647. styler.SetLevel(lineCurrent, options.foldCompact ? lev : lev & ~SC_FOLDLEVELWHITEFLAG);
  648. indentCurrent = indentNext;
  649. lineCurrent = lineNext;
  650. }
  651. // NOTE: Cannot set level of last line here because indentCurrent doesn't have
  652. // header flag set; the loop above is crafted to take care of this case!
  653. //styler.SetLevel(lineCurrent, indentCurrent);
  654. }
  655. LexerModule lmPython(SCLEX_PYTHON, LexerPython::LexerFactoryPython, "python",
  656. pythonWordListDesc);