LexRuby.cpp 69 KB

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