PageRenderTime 72ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 1ms

/Tools/MaterialEditor/wxscintilla_1.69.2/src/scintilla/src/LexRuby.cxx

https://bitbucket.org/CaptainOz/ogre
C++ | 1542 lines | 1270 code | 76 blank | 196 comment | 406 complexity | ef597fb6e749e19b39e5d422b9e35fc5 MD5 | raw file
Possible License(s): LGPL-2.1, MIT
  1. // Scintilla source code edit control
  2. /** @file LexRuby.cxx
  3. ** Lexer for Ruby.
  4. **/
  5. // Copyright 2001- by Clemens Wyss <wys@helbling.ch>
  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 <ctype.h>
  10. #include <stdio.h>
  11. #include <stdarg.h>
  12. #include "Platform.h"
  13. #include "PropSet.h"
  14. #include "Accessor.h"
  15. #include "KeyWords.h"
  16. #include "Scintilla.h"
  17. #include "SciLexer.h"
  18. #ifdef SCI_NAMESPACE
  19. using namespace Scintilla;
  20. #endif
  21. //XXX Identical to Perl, put in common area
  22. static inline bool isEOLChar(char ch) {
  23. return (ch == '\r') || (ch == '\n');
  24. }
  25. #define isSafeASCII(ch) ((unsigned int)(ch) <= 127)
  26. // This one's redundant, but makes for more readable code
  27. #define isHighBitChar(ch) ((unsigned int)(ch) > 127)
  28. static inline bool isSafeAlpha(char ch) {
  29. return (isSafeASCII(ch) && isalpha(ch)) || ch == '_';
  30. }
  31. static inline bool isSafeAlnum(char ch) {
  32. return (isSafeASCII(ch) && isalnum(ch)) || ch == '_';
  33. }
  34. static inline bool isSafeAlnumOrHigh(char ch) {
  35. return isHighBitChar(ch) || isalnum(ch) || ch == '_';
  36. }
  37. static inline bool isSafeDigit(char ch) {
  38. return isSafeASCII(ch) && isdigit(ch);
  39. }
  40. static inline bool isSafeWordcharOrHigh(char ch) {
  41. return isHighBitChar(ch) || iswordchar(ch);
  42. }
  43. static bool inline iswhitespace(char ch) {
  44. return ch == ' ' || ch == '\t';
  45. }
  46. #define MAX_KEYWORD_LENGTH 200
  47. #define STYLE_MASK 63
  48. #define actual_style(style) (style & STYLE_MASK)
  49. static bool followsDot(unsigned int pos, Accessor &styler) {
  50. styler.Flush();
  51. for (; pos >= 1; --pos) {
  52. int style = actual_style(styler.StyleAt(pos));
  53. char ch;
  54. switch (style) {
  55. case SCE_RB_DEFAULT:
  56. ch = styler[pos];
  57. if (ch == ' ' || ch == '\t') {
  58. //continue
  59. } else {
  60. return false;
  61. }
  62. break;
  63. case SCE_RB_OPERATOR:
  64. return styler[pos] == '.';
  65. default:
  66. return false;
  67. }
  68. }
  69. return false;
  70. }
  71. // Forward declarations
  72. static bool keywordIsAmbiguous(const char *prevWord);
  73. static bool keywordDoStartsLoop(int pos,
  74. Accessor &styler);
  75. static bool keywordIsModifier(const char *word,
  76. int pos,
  77. Accessor &styler);
  78. static int ClassifyWordRb(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler, char *prevWord) {
  79. char s[100];
  80. unsigned int i, j;
  81. unsigned int lim = end - start + 1; // num chars to copy
  82. if (lim >= MAX_KEYWORD_LENGTH) {
  83. lim = MAX_KEYWORD_LENGTH - 1;
  84. }
  85. for (i = start, j = 0; j < lim; i++, j++) {
  86. s[j] = styler[i];
  87. }
  88. s[j] = '\0';
  89. int chAttr;
  90. if (0 == strcmp(prevWord, "class"))
  91. chAttr = SCE_RB_CLASSNAME;
  92. else if (0 == strcmp(prevWord, "module"))
  93. chAttr = SCE_RB_MODULE_NAME;
  94. else if (0 == strcmp(prevWord, "def"))
  95. chAttr = SCE_RB_DEFNAME;
  96. else if (keywords.InList(s) && !followsDot(start - 1, styler)) {
  97. if (keywordIsAmbiguous(s)
  98. && keywordIsModifier(s, start, styler)) {
  99. // Demoted keywords are colored as keywords,
  100. // but do not affect changes in indentation.
  101. //
  102. // Consider the word 'if':
  103. // 1. <<if test ...>> : normal
  104. // 2. <<stmt if test>> : demoted
  105. // 3. <<lhs = if ...>> : normal: start a new indent level
  106. // 4. <<obj.if = 10>> : color as identifer, since it follows '.'
  107. chAttr = SCE_RB_WORD_DEMOTED;
  108. } else {
  109. chAttr = SCE_RB_WORD;
  110. }
  111. } else
  112. chAttr = SCE_RB_IDENTIFIER;
  113. styler.ColourTo(end, chAttr);
  114. if (chAttr == SCE_RB_WORD) {
  115. strcpy(prevWord, s);
  116. } else {
  117. prevWord[0] = 0;
  118. }
  119. return chAttr;
  120. }
  121. //XXX Identical to Perl, put in common area
  122. static bool isMatch(Accessor &styler, int lengthDoc, int pos, const char *val) {
  123. if ((pos + static_cast<int>(strlen(val))) >= lengthDoc) {
  124. return false;
  125. }
  126. while (*val) {
  127. if (*val != styler[pos++]) {
  128. return false;
  129. }
  130. val++;
  131. }
  132. return true;
  133. }
  134. // Do Ruby better -- find the end of the line, work back,
  135. // and then check for leading white space
  136. // Precondition: the here-doc target can be indented
  137. static bool lookingAtHereDocDelim(Accessor &styler,
  138. int pos,
  139. int lengthDoc,
  140. const char *HereDocDelim)
  141. {
  142. if (!isMatch(styler, lengthDoc, pos, HereDocDelim)) {
  143. return false;
  144. }
  145. while (--pos > 0) {
  146. char ch = styler[pos];
  147. if (isEOLChar(ch)) {
  148. return true;
  149. } else if (ch != ' ' && ch != '\t') {
  150. return false;
  151. }
  152. }
  153. return false;
  154. }
  155. //XXX Identical to Perl, put in common area
  156. static char opposite(char ch) {
  157. if (ch == '(')
  158. return ')';
  159. if (ch == '[')
  160. return ']';
  161. if (ch == '{')
  162. return '}';
  163. if (ch == '<')
  164. return '>';
  165. return ch;
  166. }
  167. // Null transitions when we see we've reached the end
  168. // and need to relex the curr char.
  169. static void redo_char(int &i, char &ch, char &chNext, char &chNext2,
  170. int &state) {
  171. i--;
  172. chNext2 = chNext;
  173. chNext = ch;
  174. state = SCE_RB_DEFAULT;
  175. }
  176. static void advance_char(int &i, char &ch, char &chNext, char &chNext2) {
  177. i++;
  178. ch = chNext;
  179. chNext = chNext2;
  180. }
  181. // precondition: startPos points to one after the EOL char
  182. static bool currLineContainsHereDelims(int& startPos,
  183. Accessor &styler) {
  184. if (startPos <= 1)
  185. return false;
  186. int pos;
  187. for (pos = startPos - 1; pos > 0; pos--) {
  188. char ch = styler.SafeGetCharAt(pos);
  189. if (isEOLChar(ch)) {
  190. // Leave the pointers where they are -- there are no
  191. // here doc delims on the current line, even if
  192. // the EOL isn't default style
  193. return false;
  194. } else {
  195. styler.Flush();
  196. if (actual_style(styler.StyleAt(pos)) == SCE_RB_HERE_DELIM) {
  197. break;
  198. }
  199. }
  200. }
  201. if (pos == 0) {
  202. return false;
  203. }
  204. // Update the pointers so we don't have to re-analyze the string
  205. startPos = pos;
  206. return true;
  207. }
  208. static bool isEmptyLine(int pos,
  209. Accessor &styler) {
  210. int spaceFlags = 0;
  211. int lineCurrent = styler.GetLine(pos);
  212. int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
  213. return (indentCurrent & SC_FOLDLEVELWHITEFLAG) != 0;
  214. }
  215. static bool RE_CanFollowKeyword(const char *keyword) {
  216. if (!strcmp(keyword, "and")
  217. || !strcmp(keyword, "begin")
  218. || !strcmp(keyword, "break")
  219. || !strcmp(keyword, "case")
  220. || !strcmp(keyword, "do")
  221. || !strcmp(keyword, "else")
  222. || !strcmp(keyword, "elsif")
  223. || !strcmp(keyword, "if")
  224. || !strcmp(keyword, "next")
  225. || !strcmp(keyword, "return")
  226. || !strcmp(keyword, "when")
  227. || !strcmp(keyword, "unless")
  228. || !strcmp(keyword, "until")
  229. || !strcmp(keyword, "not")
  230. || !strcmp(keyword, "or")) {
  231. return true;
  232. }
  233. return false;
  234. }
  235. // Look at chars up to but not including endPos
  236. // Don't look at styles in case we're looking forward
  237. static int skipWhitespace(int startPos,
  238. int endPos,
  239. Accessor &styler) {
  240. for (int i = startPos; i < endPos; i++) {
  241. if (!iswhitespace(styler[i])) {
  242. return i;
  243. }
  244. }
  245. return endPos;
  246. }
  247. // This routine looks for false positives like
  248. // undef foo, <<
  249. // There aren't too many.
  250. //
  251. // iPrev points to the start of <<
  252. static bool sureThisIsHeredoc(int iPrev,
  253. Accessor &styler,
  254. char *prevWord) {
  255. // Not so fast, since Ruby's so dynamic. Check the context
  256. // to make sure we're OK.
  257. int prevStyle;
  258. int lineStart = styler.GetLine(iPrev);
  259. int lineStartPosn = styler.LineStart(lineStart);
  260. styler.Flush();
  261. // Find the first word after some whitespace
  262. int firstWordPosn = skipWhitespace(lineStartPosn, iPrev, styler);
  263. if (firstWordPosn >= iPrev) {
  264. // Have something like {^ <<}
  265. //XXX Look at the first previous non-comment non-white line
  266. // to establish the context. Not too likely though.
  267. return true;
  268. } else {
  269. switch (prevStyle = styler.StyleAt(firstWordPosn)) {
  270. case SCE_RB_WORD:
  271. case SCE_RB_WORD_DEMOTED:
  272. case SCE_RB_IDENTIFIER:
  273. break;
  274. default:
  275. return true;
  276. }
  277. }
  278. int firstWordEndPosn = firstWordPosn;
  279. char *dst = prevWord;
  280. for (;;) {
  281. if (firstWordEndPosn >= iPrev ||
  282. styler.StyleAt(firstWordEndPosn) != prevStyle) {
  283. *dst = 0;
  284. break;
  285. }
  286. *dst++ = styler[firstWordEndPosn];
  287. firstWordEndPosn += 1;
  288. }
  289. //XXX Write a style-aware thing to regex scintilla buffer objects
  290. if (!strcmp(prevWord, "undef")
  291. || !strcmp(prevWord, "def")
  292. || !strcmp(prevWord, "alias")) {
  293. // These keywords are what we were looking for
  294. return false;
  295. }
  296. return true;
  297. }
  298. // Routine that saves us from allocating a buffer for the here-doc target
  299. // targetEndPos points one past the end of the current target
  300. static bool haveTargetMatch(int currPos,
  301. int lengthDoc,
  302. int targetStartPos,
  303. int targetEndPos,
  304. Accessor &styler) {
  305. if (lengthDoc - currPos < targetEndPos - targetStartPos) {
  306. return false;
  307. }
  308. int i, j;
  309. for (i = targetStartPos, j = currPos;
  310. i < targetEndPos && j < lengthDoc;
  311. i++, j++) {
  312. if (styler[i] != styler[j]) {
  313. return false;
  314. }
  315. }
  316. return true;
  317. }
  318. // We need a check because the form
  319. // [identifier] <<[target]
  320. // is ambiguous. The Ruby lexer/parser resolves it by
  321. // looking to see if [identifier] names a variable or a
  322. // function. If it's the first, it's the start of a here-doc.
  323. // If it's a var, it's an operator. This lexer doesn't
  324. // maintain a symbol table, so it looks ahead to see what's
  325. // going on, in cases where we have
  326. // ^[white-space]*[identifier([.|::]identifier)*][white-space]*<<[target]
  327. //
  328. // If there's no occurrence of [target] on a line, assume we don't.
  329. // return true == yes, we have no heredocs
  330. static bool sureThisIsNotHeredoc(int lt2StartPos,
  331. Accessor &styler) {
  332. int prevStyle;
  333. // Use full document, not just part we're styling
  334. int lengthDoc = styler.Length();
  335. int lineStart = styler.GetLine(lt2StartPos);
  336. int lineStartPosn = styler.LineStart(lineStart);
  337. styler.Flush();
  338. const bool definitely_not_a_here_doc = true;
  339. const bool looks_like_a_here_doc = false;
  340. // Find the first word after some whitespace
  341. int firstWordPosn = skipWhitespace(lineStartPosn, lt2StartPos, styler);
  342. if (firstWordPosn >= lt2StartPos) {
  343. return definitely_not_a_here_doc;
  344. }
  345. prevStyle = styler.StyleAt(firstWordPosn);
  346. // If we have '<<' following a keyword, it's not a heredoc
  347. if (prevStyle != SCE_RB_IDENTIFIER) {
  348. return definitely_not_a_here_doc;
  349. }
  350. int newStyle = prevStyle;
  351. // Some compilers incorrectly warn about uninit newStyle
  352. for (firstWordPosn += 1; firstWordPosn <= lt2StartPos; firstWordPosn += 1) {
  353. // Inner loop looks at the name
  354. for (; firstWordPosn <= lt2StartPos; firstWordPosn += 1) {
  355. newStyle = styler.StyleAt(firstWordPosn);
  356. if (newStyle != prevStyle) {
  357. break;
  358. }
  359. }
  360. // Do we have '::' or '.'?
  361. if (firstWordPosn < lt2StartPos && newStyle == SCE_RB_OPERATOR) {
  362. char ch = styler[firstWordPosn];
  363. if (ch == '.') {
  364. // yes
  365. } else if (ch == ':') {
  366. if (styler.StyleAt(++firstWordPosn) != SCE_RB_OPERATOR) {
  367. return definitely_not_a_here_doc;
  368. } else if (styler[firstWordPosn] != ':') {
  369. return definitely_not_a_here_doc;
  370. }
  371. } else {
  372. break;
  373. }
  374. } else {
  375. break;
  376. }
  377. }
  378. // Skip next batch of white-space
  379. firstWordPosn = skipWhitespace(firstWordPosn, lt2StartPos, styler);
  380. if (firstWordPosn != lt2StartPos) {
  381. // Have [[^ws[identifier]ws[*something_else*]ws<<
  382. return definitely_not_a_here_doc;
  383. }
  384. // OK, now 'j' will point to the current spot moving ahead
  385. int j = firstWordPosn + 1;
  386. if (styler.StyleAt(j) != SCE_RB_OPERATOR || styler[j] != '<') {
  387. // This shouldn't happen
  388. return definitely_not_a_here_doc;
  389. }
  390. int nextLineStartPosn = styler.LineStart(lineStart + 1);
  391. if (nextLineStartPosn >= lengthDoc) {
  392. return definitely_not_a_here_doc;
  393. }
  394. j = skipWhitespace(j + 1, nextLineStartPosn, styler);
  395. if (j >= lengthDoc) {
  396. return definitely_not_a_here_doc;
  397. }
  398. bool allow_indent;
  399. int target_start, target_end;
  400. // From this point on no more styling, since we're looking ahead
  401. if (styler[j] == '-') {
  402. allow_indent = true;
  403. j++;
  404. } else {
  405. allow_indent = false;
  406. }
  407. // Allow for quoted targets.
  408. char target_quote = 0;
  409. switch (styler[j]) {
  410. case '\'':
  411. case '"':
  412. case '`':
  413. target_quote = styler[j];
  414. j += 1;
  415. }
  416. if (isSafeAlnum(styler[j])) {
  417. // Init target_end because some compilers think it won't
  418. // be initialized by the time it's used
  419. target_start = target_end = j;
  420. j++;
  421. } else {
  422. return definitely_not_a_here_doc;
  423. }
  424. for (; j < lengthDoc; j++) {
  425. if (!isSafeAlnum(styler[j])) {
  426. if (target_quote && styler[j] != target_quote) {
  427. // unquoted end
  428. return definitely_not_a_here_doc;
  429. }
  430. // And for now make sure that it's a newline
  431. // don't handle arbitrary expressions yet
  432. target_end = j;
  433. if (target_quote) {
  434. // Now we can move to the character after the string delimiter.
  435. j += 1;
  436. }
  437. j = skipWhitespace(j, lengthDoc, styler);
  438. if (j >= lengthDoc) {
  439. return definitely_not_a_here_doc;
  440. } else {
  441. char ch = styler[j];
  442. if (ch == '#' || isEOLChar(ch)) {
  443. // This is OK, so break and continue;
  444. break;
  445. } else {
  446. return definitely_not_a_here_doc;
  447. }
  448. }
  449. }
  450. }
  451. // Just look at the start of each line
  452. int last_line = styler.GetLine(lengthDoc - 1);
  453. // But don't go too far
  454. if (last_line > lineStart + 50) {
  455. last_line = lineStart + 50;
  456. }
  457. for (int line_num = lineStart + 1; line_num <= last_line; line_num++) {
  458. if (allow_indent) {
  459. j = skipWhitespace(styler.LineStart(line_num), lengthDoc, styler);
  460. } else {
  461. j = styler.LineStart(line_num);
  462. }
  463. // target_end is one past the end
  464. if (haveTargetMatch(j, lengthDoc, target_start, target_end, styler)) {
  465. // We got it
  466. return looks_like_a_here_doc;
  467. }
  468. }
  469. return definitely_not_a_here_doc;
  470. }
  471. //todo: if we aren't looking at a stdio character,
  472. // move to the start of the first line that is not in a
  473. // multi-line construct
  474. static void synchronizeDocStart(unsigned int& startPos,
  475. int &length,
  476. int &initStyle,
  477. Accessor &styler,
  478. bool skipWhiteSpace=false) {
  479. styler.Flush();
  480. int style = actual_style(styler.StyleAt(startPos));
  481. switch (style) {
  482. case SCE_RB_STDIN:
  483. case SCE_RB_STDOUT:
  484. case SCE_RB_STDERR:
  485. // Don't do anything else with these.
  486. return;
  487. }
  488. int pos = startPos;
  489. // Quick way to characterize each line
  490. int lineStart;
  491. for (lineStart = styler.GetLine(pos); lineStart > 0; lineStart--) {
  492. // Now look at the style before the previous line's EOL
  493. pos = styler.LineStart(lineStart) - 1;
  494. if (pos <= 10) {
  495. lineStart = 0;
  496. break;
  497. }
  498. char ch = styler.SafeGetCharAt(pos);
  499. char chPrev = styler.SafeGetCharAt(pos - 1);
  500. if (ch == '\n' && chPrev == '\r') {
  501. pos--;
  502. }
  503. if (styler.SafeGetCharAt(pos - 1) == '\\') {
  504. // Continuation line -- keep going
  505. } else if (actual_style(styler.StyleAt(pos)) != SCE_RB_DEFAULT) {
  506. // Part of multi-line construct -- keep going
  507. } else if (currLineContainsHereDelims(pos, styler)) {
  508. // Keep going, with pos and length now pointing
  509. // at the end of the here-doc delimiter
  510. } else if (skipWhiteSpace && isEmptyLine(pos, styler)) {
  511. // Keep going
  512. } else {
  513. break;
  514. }
  515. }
  516. pos = styler.LineStart(lineStart);
  517. length += (startPos - pos);
  518. startPos = pos;
  519. initStyle = SCE_RB_DEFAULT;
  520. }
  521. static void ColouriseRbDoc(unsigned int startPos, int length, int initStyle,
  522. WordList *keywordlists[], Accessor &styler) {
  523. // Lexer for Ruby often has to backtrack to start of current style to determine
  524. // which characters are being used as quotes, how deeply nested is the
  525. // start position and what the termination string is for here documents
  526. WordList &keywords = *keywordlists[0];
  527. class HereDocCls {
  528. public:
  529. int State;
  530. // States
  531. // 0: '<<' encountered
  532. // 1: collect the delimiter
  533. // 1b: text between the end of the delimiter and the EOL
  534. // 2: here doc text (lines after the delimiter)
  535. char Quote; // the char after '<<'
  536. bool Quoted; // true if Quote in ('\'','"','`')
  537. int DelimiterLength; // strlen(Delimiter)
  538. char Delimiter[256]; // the Delimiter, limit of 256: from Perl
  539. bool CanBeIndented;
  540. HereDocCls() {
  541. State = 0;
  542. DelimiterLength = 0;
  543. Delimiter[0] = '\0';
  544. CanBeIndented = false;
  545. }
  546. };
  547. HereDocCls HereDoc;
  548. class QuoteCls {
  549. public:
  550. int Count;
  551. char Up;
  552. char Down;
  553. QuoteCls() {
  554. this->New();
  555. }
  556. void New() {
  557. Count = 0;
  558. Up = '\0';
  559. Down = '\0';
  560. }
  561. void Open(char u) {
  562. Count++;
  563. Up = u;
  564. Down = opposite(Up);
  565. }
  566. };
  567. QuoteCls Quote;
  568. int numDots = 0; // For numbers --
  569. // Don't start lexing in the middle of a num
  570. synchronizeDocStart(startPos, length, initStyle, styler, // ref args
  571. false);
  572. bool preferRE = true;
  573. int state = initStyle;
  574. int lengthDoc = startPos + length;
  575. char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
  576. prevWord[0] = '\0';
  577. if (length == 0)
  578. return;
  579. char chPrev = styler.SafeGetCharAt(startPos - 1);
  580. char chNext = styler.SafeGetCharAt(startPos);
  581. // Ruby uses a different mask because bad indentation is marked by oring with 32
  582. styler.StartAt(startPos, 127);
  583. styler.StartSegment(startPos);
  584. static int q_states[] = {SCE_RB_STRING_Q,
  585. SCE_RB_STRING_QQ,
  586. SCE_RB_STRING_QR,
  587. SCE_RB_STRING_QW,
  588. SCE_RB_STRING_QW,
  589. SCE_RB_STRING_QX};
  590. static const char* q_chars = "qQrwWx";
  591. for (int i = startPos; i < lengthDoc; i++) {
  592. char ch = chNext;
  593. chNext = styler.SafeGetCharAt(i + 1);
  594. char chNext2 = styler.SafeGetCharAt(i + 2);
  595. if (styler.IsLeadByte(ch)) {
  596. chNext = chNext2;
  597. chPrev = ' ';
  598. i += 1;
  599. continue;
  600. }
  601. // skip on DOS/Windows
  602. //No, don't, because some things will get tagged on,
  603. // so we won't recognize keywords, for example
  604. #if 0
  605. if (ch == '\r' && chNext == '\n') {
  606. continue;
  607. }
  608. #endif
  609. if (HereDoc.State == 1 && isEOLChar(ch)) {
  610. // Begin of here-doc (the line after the here-doc delimiter):
  611. HereDoc.State = 2;
  612. styler.ColourTo(i-1, state);
  613. // Don't check for a missing quote, just jump into
  614. // the here-doc state
  615. state = SCE_RB_HERE_Q;
  616. }
  617. // Regular transitions
  618. if (state == SCE_RB_DEFAULT) {
  619. if (isSafeDigit(ch)) {
  620. styler.ColourTo(i - 1, state);
  621. state = SCE_RB_NUMBER;
  622. numDots = 0;
  623. } else if (isHighBitChar(ch) || iswordstart(ch)) {
  624. styler.ColourTo(i - 1, state);
  625. state = SCE_RB_WORD;
  626. } else if (ch == '#') {
  627. styler.ColourTo(i - 1, state);
  628. state = SCE_RB_COMMENTLINE;
  629. } else if (ch == '=') {
  630. // =begin indicates the start of a comment (doc) block
  631. if (i == 0 || isEOLChar(chPrev)
  632. && chNext == 'b'
  633. && styler.SafeGetCharAt(i + 2) == 'e'
  634. && styler.SafeGetCharAt(i + 3) == 'g'
  635. && styler.SafeGetCharAt(i + 4) == 'i'
  636. && styler.SafeGetCharAt(i + 5) == 'n'
  637. && !isSafeWordcharOrHigh(styler.SafeGetCharAt(i + 6))) {
  638. styler.ColourTo(i - 1, state);
  639. state = SCE_RB_POD;
  640. } else {
  641. styler.ColourTo(i - 1, state);
  642. styler.ColourTo(i, SCE_RB_OPERATOR);
  643. preferRE = true;
  644. }
  645. } else if (ch == '"') {
  646. styler.ColourTo(i - 1, state);
  647. state = SCE_RB_STRING;
  648. Quote.New();
  649. Quote.Open(ch);
  650. } else if (ch == '\'') {
  651. styler.ColourTo(i - 1, state);
  652. state = SCE_RB_CHARACTER;
  653. Quote.New();
  654. Quote.Open(ch);
  655. } else if (ch == '`') {
  656. styler.ColourTo(i - 1, state);
  657. state = SCE_RB_BACKTICKS;
  658. Quote.New();
  659. Quote.Open(ch);
  660. } else if (ch == '@') {
  661. // Instance or class var
  662. styler.ColourTo(i - 1, state);
  663. if (chNext == '@') {
  664. state = SCE_RB_CLASS_VAR;
  665. advance_char(i, ch, chNext, chNext2); // pass by ref
  666. } else {
  667. state = SCE_RB_INSTANCE_VAR;
  668. }
  669. } else if (ch == '$') {
  670. // Check for a builtin global
  671. styler.ColourTo(i - 1, state);
  672. // Recognize it bit by bit
  673. state = SCE_RB_GLOBAL;
  674. } else if (ch == '/' && preferRE) {
  675. // Ambigous operator
  676. styler.ColourTo(i - 1, state);
  677. state = SCE_RB_REGEX;
  678. Quote.New();
  679. Quote.Open(ch);
  680. } else if (ch == '<' && chNext == '<' && chNext2 != '=') {
  681. // Recognise the '<<' symbol - either a here document or a binary op
  682. styler.ColourTo(i - 1, state);
  683. i++;
  684. chNext = chNext2;
  685. styler.ColourTo(i, SCE_RB_OPERATOR);
  686. if (! (strchr("\"\'`_-", chNext2) || isSafeAlpha(chNext2))) {
  687. // It's definitely not a here-doc,
  688. // based on Ruby's lexer/parser in the
  689. // heredoc_identifier routine.
  690. // Nothing else to do.
  691. } else if (preferRE) {
  692. if (sureThisIsHeredoc(i - 1, styler, prevWord)) {
  693. state = SCE_RB_HERE_DELIM;
  694. HereDoc.State = 0;
  695. }
  696. // else leave it in default state
  697. } else {
  698. if (sureThisIsNotHeredoc(i - 1, styler)) {
  699. // leave state as default
  700. // We don't have all the heuristics Perl has for indications
  701. // of a here-doc, because '<<' is overloadable and used
  702. // for so many other classes.
  703. } else {
  704. state = SCE_RB_HERE_DELIM;
  705. HereDoc.State = 0;
  706. }
  707. }
  708. preferRE = (state != SCE_RB_HERE_DELIM);
  709. } else if (ch == ':') {
  710. styler.ColourTo(i - 1, state);
  711. if (chNext == ':') {
  712. // Mark "::" as an operator, not symbol start
  713. styler.ColourTo(i + 1, SCE_RB_OPERATOR);
  714. advance_char(i, ch, chNext, chNext2); // pass by ref
  715. state = SCE_RB_DEFAULT;
  716. preferRE = false;
  717. } else if (isSafeWordcharOrHigh(chNext)) {
  718. state = SCE_RB_SYMBOL;
  719. } else if (strchr("[*!~+-*/%=<>&^|", chNext)) {
  720. // Do the operator analysis in-line, looking ahead
  721. // Based on the table in pickaxe 2nd ed., page 339
  722. bool doColoring = true;
  723. switch (chNext) {
  724. case '[':
  725. if (chNext2 == ']' ) {
  726. char ch_tmp = styler.SafeGetCharAt(i + 3);
  727. if (ch_tmp == '=') {
  728. i += 3;
  729. ch = ch_tmp;
  730. chNext = styler.SafeGetCharAt(i + 1);
  731. } else {
  732. i += 2;
  733. ch = chNext2;
  734. chNext = ch_tmp;
  735. }
  736. } else {
  737. doColoring = false;
  738. }
  739. break;
  740. case '*':
  741. if (chNext2 == '*') {
  742. i += 2;
  743. ch = chNext2;
  744. chNext = styler.SafeGetCharAt(i + 1);
  745. } else {
  746. advance_char(i, ch, chNext, chNext2);
  747. }
  748. break;
  749. case '!':
  750. if (chNext2 == '=' || chNext2 == '~') {
  751. i += 2;
  752. ch = chNext2;
  753. chNext = styler.SafeGetCharAt(i + 1);
  754. } else {
  755. advance_char(i, ch, chNext, chNext2);
  756. }
  757. break;
  758. case '<':
  759. if (chNext2 == '<') {
  760. i += 2;
  761. ch = chNext2;
  762. chNext = styler.SafeGetCharAt(i + 1);
  763. } else if (chNext2 == '=') {
  764. char ch_tmp = styler.SafeGetCharAt(i + 3);
  765. if (ch_tmp == '>') { // <=> operator
  766. i += 3;
  767. ch = ch_tmp;
  768. chNext = styler.SafeGetCharAt(i + 1);
  769. } else {
  770. i += 2;
  771. ch = chNext2;
  772. chNext = ch_tmp;
  773. }
  774. } else {
  775. advance_char(i, ch, chNext, chNext2);
  776. }
  777. break;
  778. default:
  779. // Simple one-character operators
  780. advance_char(i, ch, chNext, chNext2);
  781. break;
  782. }
  783. if (doColoring) {
  784. styler.ColourTo(i, SCE_RB_SYMBOL);
  785. state = SCE_RB_DEFAULT;
  786. }
  787. } else if (!preferRE) {
  788. // Don't color symbol strings (yet)
  789. // Just color the ":" and color rest as string
  790. styler.ColourTo(i, SCE_RB_SYMBOL);
  791. state = SCE_RB_DEFAULT;
  792. } else {
  793. styler.ColourTo(i, SCE_RB_OPERATOR);
  794. state = SCE_RB_DEFAULT;
  795. preferRE = true;
  796. }
  797. } else if (ch == '%') {
  798. styler.ColourTo(i - 1, state);
  799. bool have_string = false;
  800. if (strchr(q_chars, chNext) && !isSafeWordcharOrHigh(chNext2)) {
  801. Quote.New();
  802. const char *hit = strchr(q_chars, chNext);
  803. if (hit != NULL) {
  804. state = q_states[hit - q_chars];
  805. Quote.Open(chNext2);
  806. i += 2;
  807. ch = chNext2;
  808. chNext = styler.SafeGetCharAt(i + 1);
  809. have_string = true;
  810. }
  811. } else if (!isSafeWordcharOrHigh(chNext)) {
  812. // Ruby doesn't allow high bit chars here,
  813. // but the editor host might
  814. state = SCE_RB_STRING_QQ;
  815. Quote.Open(chNext);
  816. advance_char(i, ch, chNext, chNext2); // pass by ref
  817. have_string = true;
  818. }
  819. if (!have_string) {
  820. styler.ColourTo(i, SCE_RB_OPERATOR);
  821. // stay in default
  822. preferRE = true;
  823. }
  824. } else if (isoperator(ch) || ch == '.') {
  825. styler.ColourTo(i - 1, state);
  826. styler.ColourTo(i, SCE_RB_OPERATOR);
  827. // If we're ending an expression or block,
  828. // assume it ends an object, and the ambivalent
  829. // constructs are binary operators
  830. //
  831. // So if we don't have one of these chars,
  832. // we aren't ending an object exp'n, and ops
  833. // like : << / are unary operators.
  834. preferRE = (strchr(")}].", ch) == NULL);
  835. // Stay in default state
  836. } else if (isEOLChar(ch)) {
  837. // Make sure it's a true line-end, with no backslash
  838. if ((ch == '\r' || (ch == '\n' && chPrev != '\r'))
  839. && chPrev != '\\') {
  840. // Assume we've hit the end of the statement.
  841. preferRE = true;
  842. }
  843. }
  844. } else if (state == SCE_RB_WORD) {
  845. if (ch == '.' || !isSafeWordcharOrHigh(ch)) {
  846. // Words include x? in all contexts,
  847. // and <letters>= after either 'def' or a dot
  848. // Move along until a complete word is on our left
  849. // Default accessor treats '.' as word-chars,
  850. // but we don't for now.
  851. if (ch == '='
  852. && isSafeWordcharOrHigh(chPrev)
  853. && (chNext == '('
  854. || strchr(" \t\n\r", chNext) != NULL)
  855. && (!strcmp(prevWord, "def")
  856. || followsDot(styler.GetStartSegment(), styler))) {
  857. // <name>= is a name only when being def'd -- Get it the next time
  858. // This means that <name>=<name> is always lexed as
  859. // <name>, (op, =), <name>
  860. } else if ((ch == '?' || ch == '!')
  861. && isSafeWordcharOrHigh(chPrev)
  862. && !isSafeWordcharOrHigh(chNext)) {
  863. // <name>? is a name -- Get it the next time
  864. // But <name>?<name> is always lexed as
  865. // <name>, (op, ?), <name>
  866. // Same with <name>! to indicate a method that
  867. // modifies its target
  868. } else if (isEOLChar(ch)
  869. && isMatch(styler, lengthDoc, i - 7, "__END__")) {
  870. styler.ColourTo(i, SCE_RB_DATASECTION);
  871. state = SCE_RB_DATASECTION;
  872. // No need to handle this state -- we'll just move to the end
  873. preferRE = false;
  874. } else {
  875. int wordStartPos = styler.GetStartSegment();
  876. int word_style = ClassifyWordRb(wordStartPos, i - 1, keywords, styler, prevWord);
  877. switch (word_style) {
  878. case SCE_RB_WORD:
  879. preferRE = RE_CanFollowKeyword(prevWord);
  880. break;
  881. case SCE_RB_WORD_DEMOTED:
  882. preferRE = true;
  883. break;
  884. case SCE_RB_IDENTIFIER:
  885. if (isMatch(styler, lengthDoc, wordStartPos, "print")) {
  886. preferRE = true;
  887. } else if (isEOLChar(ch)) {
  888. preferRE = true;
  889. } else {
  890. preferRE = false;
  891. }
  892. break;
  893. default:
  894. preferRE = false;
  895. }
  896. if (ch == '.') {
  897. // We might be redefining an operator-method
  898. preferRE = false;
  899. }
  900. // And if it's the first
  901. redo_char(i, ch, chNext, chNext2, state); // pass by ref
  902. }
  903. }
  904. } else if (state == SCE_RB_NUMBER) {
  905. if (isSafeAlnumOrHigh(ch) || ch == '_') {
  906. // Keep going
  907. } else if (ch == '.' && ++numDots == 1) {
  908. // Keep going
  909. } else {
  910. styler.ColourTo(i - 1, state);
  911. redo_char(i, ch, chNext, chNext2, state); // pass by ref
  912. preferRE = false;
  913. }
  914. } else if (state == SCE_RB_COMMENTLINE) {
  915. if (isEOLChar(ch)) {
  916. styler.ColourTo(i - 1, state);
  917. state = SCE_RB_DEFAULT;
  918. // Use whatever setting we had going into the comment
  919. }
  920. } else if (state == SCE_RB_HERE_DELIM) {
  921. // See the comment for SCE_RB_HERE_DELIM in LexPerl.cxx
  922. // Slightly different: if we find an immediate '-',
  923. // the target can appear indented.
  924. if (HereDoc.State == 0) { // '<<' encountered
  925. HereDoc.State = 1;
  926. HereDoc.DelimiterLength = 0;
  927. if (ch == '-') {
  928. HereDoc.CanBeIndented = true;
  929. advance_char(i, ch, chNext, chNext2); // pass by ref
  930. } else {
  931. HereDoc.CanBeIndented = false;
  932. }
  933. if (isEOLChar(ch)) {
  934. // Bail out of doing a here doc if there's no target
  935. state = SCE_RB_DEFAULT;
  936. preferRE = false;
  937. } else {
  938. HereDoc.Quote = ch;
  939. if (ch == '\'' || ch == '"' || ch == '`') {
  940. HereDoc.Quoted = true;
  941. HereDoc.Delimiter[0] = '\0';
  942. } else {
  943. HereDoc.Quoted = false;
  944. HereDoc.Delimiter[0] = ch;
  945. HereDoc.Delimiter[1] = '\0';
  946. HereDoc.DelimiterLength = 1;
  947. }
  948. }
  949. } else if (HereDoc.State == 1) { // collect the delimiter
  950. if (isEOLChar(ch)) {
  951. // End the quote now, and go back for more
  952. styler.ColourTo(i - 1, state);
  953. state = SCE_RB_DEFAULT;
  954. i--;
  955. chNext = ch;
  956. chNext2 = chNext;
  957. preferRE = false;
  958. } else if (HereDoc.Quoted) {
  959. if (ch == HereDoc.Quote) { // closing quote => end of delimiter
  960. styler.ColourTo(i, state);
  961. state = SCE_RB_DEFAULT;
  962. preferRE = false;
  963. } else {
  964. if (ch == '\\' && !isEOLChar(chNext)) {
  965. advance_char(i, ch, chNext, chNext2);
  966. }
  967. HereDoc.Delimiter[HereDoc.DelimiterLength++] = ch;
  968. HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
  969. }
  970. } else { // an unquoted here-doc delimiter
  971. if (isSafeAlnumOrHigh(ch) || ch == '_') {
  972. HereDoc.Delimiter[HereDoc.DelimiterLength++] = ch;
  973. HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
  974. } else {
  975. styler.ColourTo(i - 1, state);
  976. redo_char(i, ch, chNext, chNext2, state);
  977. preferRE = false;
  978. }
  979. }
  980. if (HereDoc.DelimiterLength >= static_cast<int>(sizeof(HereDoc.Delimiter)) - 1) {
  981. styler.ColourTo(i - 1, state);
  982. state = SCE_RB_ERROR;
  983. preferRE = false;
  984. }
  985. }
  986. } else if (state == SCE_RB_HERE_Q) {
  987. // Not needed: HereDoc.State == 2
  988. // Indentable here docs: look backwards
  989. // Non-indentable: look forwards, like in Perl
  990. //
  991. // Why: so we can quickly resolve things like <<-" abc"
  992. if (!HereDoc.CanBeIndented) {
  993. if (isEOLChar(chPrev)
  994. && isMatch(styler, lengthDoc, i, HereDoc.Delimiter)) {
  995. styler.ColourTo(i - 1, state);
  996. i += HereDoc.DelimiterLength - 1;
  997. chNext = styler.SafeGetCharAt(i + 1);
  998. if (isEOLChar(chNext)) {
  999. styler.ColourTo(i, SCE_RB_HERE_DELIM);
  1000. state = SCE_RB_DEFAULT;
  1001. HereDoc.State = 0;
  1002. preferRE = false;
  1003. }
  1004. // Otherwise we skipped through the here doc faster.
  1005. }
  1006. } else if (isEOLChar(chNext)
  1007. && lookingAtHereDocDelim(styler,
  1008. i - HereDoc.DelimiterLength + 1,
  1009. lengthDoc,
  1010. HereDoc.Delimiter)) {
  1011. styler.ColourTo(i - 1 - HereDoc.DelimiterLength, state);
  1012. styler.ColourTo(i, SCE_RB_HERE_DELIM);
  1013. state = SCE_RB_DEFAULT;
  1014. preferRE = false;
  1015. HereDoc.State = 0;
  1016. }
  1017. } else if (state == SCE_RB_CLASS_VAR
  1018. || state == SCE_RB_INSTANCE_VAR
  1019. || state == SCE_RB_SYMBOL) {
  1020. if (!isSafeWordcharOrHigh(ch)) {
  1021. styler.ColourTo(i - 1, state);
  1022. redo_char(i, ch, chNext, chNext2, state); // pass by ref
  1023. preferRE = false;
  1024. }
  1025. } else if (state == SCE_RB_GLOBAL) {
  1026. if (!isSafeWordcharOrHigh(ch)) {
  1027. // handle special globals here as well
  1028. if (chPrev == '$') {
  1029. if (ch == '-') {
  1030. // Include the next char, like $-a
  1031. advance_char(i, ch, chNext, chNext2);
  1032. }
  1033. styler.ColourTo(i, state);
  1034. state = SCE_RB_DEFAULT;
  1035. } else {
  1036. styler.ColourTo(i - 1, state);
  1037. redo_char(i, ch, chNext, chNext2, state); // pass by ref
  1038. }
  1039. preferRE = false;
  1040. }
  1041. } else if (state == SCE_RB_POD) {
  1042. // PODs end with ^=end\s, -- any whitespace can follow =end
  1043. if (strchr(" \t\n\r", ch) != NULL
  1044. && i > 5
  1045. && isEOLChar(styler[i - 5])
  1046. && isMatch(styler, lengthDoc, i - 4, "=end")) {
  1047. styler.ColourTo(i - 1, state);
  1048. state = SCE_RB_DEFAULT;
  1049. preferRE = false;
  1050. }
  1051. } else if (state == SCE_RB_REGEX || state == SCE_RB_STRING_QR) {
  1052. if (ch == '\\' && Quote.Up != '\\') {
  1053. // Skip one
  1054. advance_char(i, ch, chNext, chNext2);
  1055. } else if (ch == Quote.Down) {
  1056. Quote.Count--;
  1057. if (Quote.Count == 0) {
  1058. // Include the options
  1059. while (isSafeAlpha(chNext)) {
  1060. i++;
  1061. ch = chNext;
  1062. chNext = styler.SafeGetCharAt(i + 1);
  1063. }
  1064. styler.ColourTo(i, state);
  1065. state = SCE_RB_DEFAULT;
  1066. preferRE = false;
  1067. }
  1068. } else if (ch == Quote.Up) {
  1069. // Only if close quoter != open quoter
  1070. Quote.Count++;
  1071. } else if (ch == '#' ) {
  1072. //todo: distinguish comments from pound chars
  1073. // for now, handle as comment
  1074. styler.ColourTo(i - 1, state);
  1075. bool inEscape = false;
  1076. while (++i < lengthDoc) {
  1077. ch = styler.SafeGetCharAt(i);
  1078. if (ch == '\\') {
  1079. inEscape = true;
  1080. } else if (isEOLChar(ch)) {
  1081. // Comment inside a regex
  1082. styler.ColourTo(i - 1, SCE_RB_COMMENTLINE);
  1083. break;
  1084. } else if (inEscape) {
  1085. inEscape = false; // don't look at char
  1086. } else if (ch == Quote.Down) {
  1087. // Have the regular handler deal with this
  1088. // to get trailing modifiers.
  1089. i--;
  1090. ch = styler[i];
  1091. break;
  1092. }
  1093. }
  1094. chNext = styler.SafeGetCharAt(i + 1);
  1095. chNext2 = styler.SafeGetCharAt(i + 2);
  1096. }
  1097. // Quotes of all kinds...
  1098. } else if (state == SCE_RB_STRING_Q || state == SCE_RB_STRING_QQ ||
  1099. state == SCE_RB_STRING_QX || state == SCE_RB_STRING_QW ||
  1100. state == SCE_RB_STRING || state == SCE_RB_CHARACTER ||
  1101. state == SCE_RB_BACKTICKS) {
  1102. if (!Quote.Down && !isspacechar(ch)) {
  1103. Quote.Open(ch);
  1104. } else if (ch == '\\' && Quote.Up != '\\') {
  1105. //Riddle me this: Is it safe to skip *every* escaped char?
  1106. advance_char(i, ch, chNext, chNext2);
  1107. } else if (ch == Quote.Down) {
  1108. Quote.Count--;
  1109. if (Quote.Count == 0) {
  1110. styler.ColourTo(i, state);
  1111. state = SCE_RB_DEFAULT;
  1112. preferRE = false;
  1113. }
  1114. } else if (ch == Quote.Up) {
  1115. Quote.Count++;
  1116. }
  1117. }
  1118. if (state == SCE_RB_ERROR) {
  1119. break;
  1120. }
  1121. chPrev = ch;
  1122. }
  1123. if (state == SCE_RB_WORD) {
  1124. // We've ended on a word, possibly at EOF, and need to
  1125. // classify it.
  1126. (void) ClassifyWordRb(styler.GetStartSegment(), lengthDoc - 1, keywords, styler, prevWord);
  1127. } else {
  1128. styler.ColourTo(lengthDoc - 1, state);
  1129. }
  1130. }
  1131. // Helper functions for folding, disambiguation keywords
  1132. // Assert that there are no high-bit chars
  1133. static void getPrevWord(int pos,
  1134. char *prevWord,
  1135. Accessor &styler,
  1136. int word_state)
  1137. {
  1138. int i;
  1139. styler.Flush();
  1140. for (i = pos - 1; i > 0; i--) {
  1141. if (actual_style(styler.StyleAt(i)) != word_state) {
  1142. i++;
  1143. break;
  1144. }
  1145. }
  1146. if (i < pos - MAX_KEYWORD_LENGTH) // overflow
  1147. i = pos - MAX_KEYWORD_LENGTH;
  1148. char *dst = prevWord;
  1149. for (; i <= pos; i++) {
  1150. *dst++ = styler[i];
  1151. }
  1152. *dst = 0;
  1153. }
  1154. static bool keywordIsAmbiguous(const char *prevWord)
  1155. {
  1156. // Order from most likely used to least likely
  1157. // Lots of ways to do a loop in Ruby besides 'while/until'
  1158. if (!strcmp(prevWord, "if")
  1159. || !strcmp(prevWord, "do")
  1160. || !strcmp(prevWord, "while")
  1161. || !strcmp(prevWord, "unless")
  1162. || !strcmp(prevWord, "until")) {
  1163. return true;
  1164. } else {
  1165. return false;
  1166. }
  1167. }
  1168. // Demote keywords in the following conditions:
  1169. // if, while, unless, until modify a statement
  1170. // do after a while or until, as a noise word (like then after if)
  1171. static bool keywordIsModifier(const char *word,
  1172. int pos,
  1173. Accessor &styler)
  1174. {
  1175. if (word[0] == 'd' && word[1] == 'o' && !word[2]) {
  1176. return keywordDoStartsLoop(pos, styler);
  1177. }
  1178. char ch;
  1179. int style = SCE_RB_DEFAULT;
  1180. int lineStart = styler.GetLine(pos);
  1181. int lineStartPosn = styler.LineStart(lineStart);
  1182. styler.Flush();
  1183. while (--pos >= lineStartPosn) {
  1184. style = actual_style(styler.StyleAt(pos));
  1185. if (style == SCE_RB_DEFAULT) {
  1186. if (iswhitespace(ch = styler[pos])) {
  1187. //continue
  1188. } else if (ch == '\r' || ch == '\n') {
  1189. // Scintilla's LineStart() and GetLine() routines aren't
  1190. // platform-independent, so if we have text prepared with
  1191. // a different system we can't rely on it.
  1192. return false;
  1193. }
  1194. } else {
  1195. break;
  1196. }
  1197. }
  1198. if (pos < lineStartPosn) {
  1199. return false; //XXX not quite right if the prev line is a continuation
  1200. }
  1201. // First things where the action is unambiguous
  1202. switch (style) {
  1203. case SCE_RB_DEFAULT:
  1204. case SCE_RB_COMMENTLINE:
  1205. case SCE_RB_POD:
  1206. case SCE_RB_CLASSNAME:
  1207. case SCE_RB_DEFNAME:
  1208. case SCE_RB_MODULE_NAME:
  1209. return false;
  1210. case SCE_RB_OPERATOR:
  1211. break;
  1212. case SCE_RB_WORD:
  1213. // Watch out for uses of 'else if'
  1214. //XXX: Make a list of other keywords where 'if' isn't a modifier
  1215. // and can appear legitimately
  1216. // Formulate this to avoid warnings from most compilers
  1217. if (strcmp(word, "if") == 0) {
  1218. char prevWord[MAX_KEYWORD_LENGTH + 1];
  1219. getPrevWord(pos, prevWord, styler, SCE_RB_WORD);
  1220. return strcmp(prevWord, "else") != 0;
  1221. }
  1222. return true;
  1223. default:
  1224. return true;
  1225. }
  1226. // Assume that if the keyword follows an operator,
  1227. // usually it's a block assignment, like
  1228. // a << if x then y else z
  1229. ch = styler[pos];
  1230. switch (ch) {
  1231. case ')':
  1232. case ']':
  1233. case '}':
  1234. return true;
  1235. default:
  1236. return false;
  1237. }
  1238. }
  1239. #define WHILE_BACKWARDS "elihw"
  1240. #define UNTIL_BACKWARDS "litnu"
  1241. // Nothing fancy -- look to see if we follow a while/until somewhere
  1242. // on the current line
  1243. static bool keywordDoStartsLoop(int pos,
  1244. Accessor &styler)
  1245. {
  1246. char ch;
  1247. int style;
  1248. int lineStart = styler.GetLine(pos);
  1249. int lineStartPosn = styler.LineStart(lineStart);
  1250. styler.Flush();
  1251. while (--pos >= lineStartPosn) {
  1252. style = actual_style(styler.StyleAt(pos));
  1253. if (style == SCE_RB_DEFAULT) {
  1254. if ((ch = styler[pos]) == '\r' || ch == '\n') {
  1255. // Scintilla's LineStart() and GetLine() routines aren't
  1256. // platform-independent, so if we have text prepared with
  1257. // a different system we can't rely on it.
  1258. return false;
  1259. }
  1260. } else if (style == SCE_RB_WORD) {
  1261. // Check for while or until, but write the word in backwards
  1262. char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
  1263. char *dst = prevWord;
  1264. int wordLen = 0;
  1265. int start_word;
  1266. for (start_word = pos;
  1267. start_word >= lineStartPosn && actual_style(styler.StyleAt(start_word)) == SCE_RB_WORD;
  1268. start_word--) {
  1269. if (++wordLen < MAX_KEYWORD_LENGTH) {
  1270. *dst++ = styler[start_word];
  1271. }
  1272. }
  1273. *dst = 0;
  1274. // Did we see our keyword?
  1275. if (!strcmp(prevWord, WHILE_BACKWARDS)
  1276. || !strcmp(prevWord, UNTIL_BACKWARDS)) {
  1277. return true;
  1278. }
  1279. // We can move pos to the beginning of the keyword, and then
  1280. // accept another decrement, as we can never have two contiguous
  1281. // keywords:
  1282. // word1 word2
  1283. // ^
  1284. // <- move to start_word
  1285. // ^
  1286. // <- loop decrement
  1287. // ^ # pointing to end of word1 is fine
  1288. pos = start_word;
  1289. }
  1290. }
  1291. return false;
  1292. }
  1293. /*
  1294. * Folding Ruby
  1295. *
  1296. * The language is quite complex to analyze without a full parse.
  1297. * For example, this line shouldn't affect fold level:
  1298. *
  1299. * print "hello" if feeling_friendly?
  1300. *
  1301. * Neither should this:
  1302. *
  1303. * print "hello" \
  1304. * if feeling_friendly?
  1305. *
  1306. *
  1307. * But this should:
  1308. *
  1309. * if feeling_friendly? #++
  1310. * print "hello" \
  1311. * print "goodbye"
  1312. * end #--
  1313. *
  1314. * So we cheat, by actually looking at the existing indentation
  1315. * levels for each line, and just echoing it back. Like Python.
  1316. * Then if we get better at it, we'll take braces into consideration,
  1317. * which always affect folding levels.
  1318. * How the keywords should work:
  1319. * No effect:
  1320. * __FILE__ __LINE__ BEGIN END alias and
  1321. * defined? false in nil not or self super then
  1322. * true undef
  1323. * Always increment:
  1324. * begin class def do for module when {
  1325. *
  1326. * Always decrement:
  1327. * end }
  1328. *
  1329. * Increment if these start a statement
  1330. * if unless until while -- do nothing if they're modifiers
  1331. * These end a block if there's no modifier, but don't bother
  1332. * break next redo retry return yield
  1333. *
  1334. * These temporarily de-indent, but re-indent
  1335. * case else elsif ensure rescue
  1336. *
  1337. * This means that the folder reflects indentation rather
  1338. * than setting it. The language-service updates indentation
  1339. * when users type return and finishes entering de-denters.
  1340. *
  1341. * Later offer to fold POD, here-docs, strings, and blocks of comments
  1342. */
  1343. static void FoldRbDoc(unsigned int startPos, int length, int initStyle,
  1344. WordList *[], Accessor &styler) {
  1345. const bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
  1346. bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
  1347. synchronizeDocStart(startPos, length, initStyle, styler, // ref args
  1348. false);
  1349. unsigned int endPos = startPos + length;
  1350. int visibleChars = 0;
  1351. int lineCurrent = styler.GetLine(startPos);
  1352. int levelPrev = startPos == 0 ? 0 : (styler.LevelAt(lineCurrent)
  1353. & SC_FOLDLEVELNUMBERMASK
  1354. & ~SC_FOLDLEVELBASE);
  1355. int levelCurrent = levelPrev;
  1356. char chNext = styler[startPos];
  1357. int styleNext = styler.StyleAt(startPos);
  1358. int stylePrev = startPos <= 1 ? SCE_RB_DEFAULT : styler.StyleAt(startPos - 1);
  1359. bool buffer_ends_with_eol = false;
  1360. for (unsigned int i = startPos; i < endPos; i++) {
  1361. char ch = chNext;
  1362. chNext = styler.SafeGetCharAt(i + 1);
  1363. int style = styleNext;
  1364. styleNext = styler.StyleAt(i + 1);
  1365. bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
  1366. if (style == SCE_RB_COMMENTLINE) {
  1367. if (foldComment && stylePrev != SCE_RB_COMMENTLINE) {
  1368. if (chNext == '{') {
  1369. levelCurrent++;
  1370. } else if (chNext == '}') {
  1371. levelCurrent--;
  1372. }
  1373. }
  1374. } else if (style == SCE_RB_OPERATOR) {
  1375. if (strchr("[{(", ch)) {
  1376. levelCurrent++;
  1377. } else if (strchr(")}]", ch)) {
  1378. // Don't decrement below 0
  1379. if (levelCurrent > 0)
  1380. levelCurrent--;
  1381. }
  1382. } else if (style == SCE_RB_WORD && styleNext != SCE_RB_WORD) {
  1383. // Look at the keyword on the left and decide what to do
  1384. char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
  1385. prevWord[0] = 0;
  1386. getPrevWord(i, prevWord, styler, SCE_RB_WORD);
  1387. if (!strcmp(prevWord, "end")) {
  1388. // Don't decrement below 0
  1389. if (levelCurrent > 0)
  1390. levelCurrent--;
  1391. } else if ( !strcmp(prevWord, "if")
  1392. || !strcmp(prevWord, "def")
  1393. || !strcmp(prevWord, "class")
  1394. || !strcmp(prevWord, "module")
  1395. || !strcmp(prevWord, "begin")
  1396. || !strcmp(prevWord, "case")
  1397. || !strcmp(prevWord, "do")
  1398. || !strcmp(prevWord, "while")
  1399. || !strcmp(prevWord, "unless")
  1400. || !strcmp(prevWord, "until")
  1401. || !strcmp(prevWord, "for")
  1402. ) {
  1403. levelCurrent++;
  1404. }
  1405. }
  1406. if (atEOL) {
  1407. int lev = levelPrev;
  1408. if (visibleChars == 0 && foldCompact)
  1409. lev |= SC_FOLDLEVELWHITEFLAG;
  1410. if ((levelCurrent > levelPrev) && (visibleChars > 0))
  1411. lev |= SC_FOLDLEVELHEADERFLAG;
  1412. styler.SetLevel(lineCurrent, lev|SC_FOLDLEVELBASE);
  1413. lineCurrent++;
  1414. levelPrev = levelCurrent;
  1415. visibleChars = 0;
  1416. buffer_ends_with_eol = true;
  1417. } else if (!isspacechar(ch)) {
  1418. visibleChars++;
  1419. buffer_ends_with_eol = false;
  1420. }
  1421. }
  1422. // Fill in the real level of the next line, keeping the current flags as they will be filled in later
  1423. if (!buffer_ends_with_eol) {
  1424. lineCurrent++;
  1425. int new_lev = levelCurrent;
  1426. if (visibleChars == 0 && foldCompact)
  1427. new_lev |= SC_FOLDLEVELWHITEFLAG;
  1428. if ((levelCurrent > levelPrev) && (visibleChars > 0))
  1429. new_lev |= SC_FOLDLEVELHEADERFLAG;
  1430. levelCurrent = new_lev;
  1431. }
  1432. styler.SetLevel(lineCurrent, levelCurrent|SC_FOLDLEVELBASE);
  1433. }
  1434. static const char * const rubyWordListDesc[] = {
  1435. "Keywords",
  1436. 0
  1437. };
  1438. LexerModule lmRuby(SCLEX_RUBY, ColouriseRbDoc, "ruby", FoldRbDoc, rubyWordListDesc);