1 // Scintilla source code edit control
5 // Copyright 2001- by Clemens Wyss <wys@helbling.ch>
6 // The License.txt file describes the conditions under which this software may be distributed.
19 #include "Scintilla.h"
23 using namespace Scintilla
;
26 //XXX Identical to Perl, put in common area
27 static inline bool isEOLChar(char ch
) {
28 return (ch
== '\r') || (ch
== '\n');
31 #define isSafeASCII(ch) ((unsigned int)(ch) <= 127)
32 // This one's redundant, but makes for more readable code
33 #define isHighBitChar(ch) ((unsigned int)(ch) > 127)
35 static inline bool isSafeAlpha(char ch
) {
36 return (isSafeASCII(ch
) && isalpha(ch
)) || ch
== '_';
39 static inline bool isSafeAlnum(char ch
) {
40 return (isSafeASCII(ch
) && isalnum(ch
)) || ch
== '_';
43 static inline bool isSafeAlnumOrHigh(char ch
) {
44 return isHighBitChar(ch
) || isalnum(ch
) || ch
== '_';
47 static inline bool isSafeDigit(char ch
) {
48 return isSafeASCII(ch
) && isdigit(ch
);
51 static inline bool isSafeWordcharOrHigh(char ch
) {
52 // Error: scintilla's KeyWords.h includes '.' as a word-char
53 // we want to separate things that can take methods from the
55 return isHighBitChar(ch
) || isalnum(ch
) || ch
== '_';
58 static bool inline iswhitespace(char ch
) {
59 return ch
== ' ' || ch
== '\t';
62 #define MAX_KEYWORD_LENGTH 200
65 #define actual_style(style) (style & STYLE_MASK)
67 static bool followsDot(unsigned int pos
, Accessor
&styler
) {
69 for (; pos
>= 1; --pos
) {
70 int style
= actual_style(styler
.StyleAt(pos
));
75 if (ch
== ' ' || ch
== '\t') {
83 return styler
[pos
] == '.';
92 // Forward declarations
93 static bool keywordIsAmbiguous(const char *prevWord
);
94 static bool keywordDoStartsLoop(int pos
,
96 static bool keywordIsModifier(const char *word
,
100 static int ClassifyWordRb(unsigned int start
, unsigned int end
, WordList
&keywords
, Accessor
&styler
, char *prevWord
) {
101 char s
[MAX_KEYWORD_LENGTH
];
103 unsigned int lim
= end
- start
+ 1; // num chars to copy
104 if (lim
>= MAX_KEYWORD_LENGTH
) {
105 lim
= MAX_KEYWORD_LENGTH
- 1;
107 for (i
= start
, j
= 0; j
< lim
; i
++, j
++) {
112 if (0 == strcmp(prevWord
, "class"))
113 chAttr
= SCE_RB_CLASSNAME
;
114 else if (0 == strcmp(prevWord
, "module"))
115 chAttr
= SCE_RB_MODULE_NAME
;
116 else if (0 == strcmp(prevWord
, "def"))
117 chAttr
= SCE_RB_DEFNAME
;
118 else if (keywords
.InList(s
) && !followsDot(start
- 1, styler
)) {
119 if (keywordIsAmbiguous(s
)
120 && keywordIsModifier(s
, start
, styler
)) {
122 // Demoted keywords are colored as keywords,
123 // but do not affect changes in indentation.
125 // Consider the word 'if':
126 // 1. <<if test ...>> : normal
127 // 2. <<stmt if test>> : demoted
128 // 3. <<lhs = if ...>> : normal: start a new indent level
129 // 4. <<obj.if = 10>> : color as identifer, since it follows '.'
131 chAttr
= SCE_RB_WORD_DEMOTED
;
133 chAttr
= SCE_RB_WORD
;
136 chAttr
= SCE_RB_IDENTIFIER
;
137 styler
.ColourTo(end
, chAttr
);
138 if (chAttr
== SCE_RB_WORD
) {
147 //XXX Identical to Perl, put in common area
148 static bool isMatch(Accessor
&styler
, int lengthDoc
, int pos
, const char *val
) {
149 if ((pos
+ static_cast<int>(strlen(val
))) >= lengthDoc
) {
153 if (*val
!= styler
[pos
++]) {
161 // Do Ruby better -- find the end of the line, work back,
162 // and then check for leading white space
164 // Precondition: the here-doc target can be indented
165 static bool lookingAtHereDocDelim(Accessor
&styler
,
168 const char *HereDocDelim
)
170 if (!isMatch(styler
, lengthDoc
, pos
, HereDocDelim
)) {
174 char ch
= styler
[pos
];
177 } else if (ch
!= ' ' && ch
!= '\t') {
184 //XXX Identical to Perl, put in common area
185 static char opposite(char ch
) {
197 // Null transitions when we see we've reached the end
198 // and need to relex the curr char.
200 static void redo_char(int &i
, char &ch
, char &chNext
, char &chNext2
,
205 state
= SCE_RB_DEFAULT
;
208 static void advance_char(int &i
, char &ch
, char &chNext
, char &chNext2
) {
214 // precondition: startPos points to one after the EOL char
215 static bool currLineContainsHereDelims(int& startPos
,
221 for (pos
= startPos
- 1; pos
> 0; pos
--) {
222 char ch
= styler
.SafeGetCharAt(pos
);
224 // Leave the pointers where they are -- there are no
225 // here doc delims on the current line, even if
226 // the EOL isn't default style
231 if (actual_style(styler
.StyleAt(pos
)) == SCE_RB_HERE_DELIM
) {
239 // Update the pointers so we don't have to re-analyze the string
244 // This class is used by the enter and exit methods, so it needs
245 // to be hoisted out of the function.
265 QuoteCls(const QuoteCls
& q
) {
266 // copy constructor -- use this for copying in
271 QuoteCls
& operator=(const QuoteCls
& q
) { // assignment constructor
283 static void enterInnerExpression(int *p_inner_string_types
,
284 int *p_inner_expn_brace_counts
,
285 QuoteCls
*p_inner_quotes
,
286 int& inner_string_count
,
291 p_inner_string_types
[inner_string_count
] = state
;
292 state
= SCE_RB_DEFAULT
;
293 p_inner_expn_brace_counts
[inner_string_count
] = brace_counts
;
295 p_inner_quotes
[inner_string_count
] = curr_quote
;
296 ++inner_string_count
;
299 static void exitInnerExpression(int *p_inner_string_types
,
300 int *p_inner_expn_brace_counts
,
301 QuoteCls
*p_inner_quotes
,
302 int& inner_string_count
,
307 --inner_string_count
;
308 state
= p_inner_string_types
[inner_string_count
];
309 brace_counts
= p_inner_expn_brace_counts
[inner_string_count
];
310 curr_quote
= p_inner_quotes
[inner_string_count
];
313 static bool isEmptyLine(int pos
,
316 int lineCurrent
= styler
.GetLine(pos
);
317 int indentCurrent
= styler
.IndentAmount(lineCurrent
, &spaceFlags
, NULL
);
318 return (indentCurrent
& SC_FOLDLEVELWHITEFLAG
) != 0;
321 static bool RE_CanFollowKeyword(const char *keyword
) {
322 if (!strcmp(keyword
, "and")
323 || !strcmp(keyword
, "begin")
324 || !strcmp(keyword
, "break")
325 || !strcmp(keyword
, "case")
326 || !strcmp(keyword
, "do")
327 || !strcmp(keyword
, "else")
328 || !strcmp(keyword
, "elsif")
329 || !strcmp(keyword
, "if")
330 || !strcmp(keyword
, "next")
331 || !strcmp(keyword
, "return")
332 || !strcmp(keyword
, "when")
333 || !strcmp(keyword
, "unless")
334 || !strcmp(keyword
, "until")
335 || !strcmp(keyword
, "not")
336 || !strcmp(keyword
, "or")) {
342 // Look at chars up to but not including endPos
343 // Don't look at styles in case we're looking forward
345 static int skipWhitespace(int startPos
,
348 for (int i
= startPos
; i
< endPos
; i
++) {
349 if (!iswhitespace(styler
[i
])) {
356 // This routine looks for false positives like
358 // There aren't too many.
360 // iPrev points to the start of <<
362 static bool sureThisIsHeredoc(int iPrev
,
366 // Not so fast, since Ruby's so dynamic. Check the context
367 // to make sure we're OK.
369 int lineStart
= styler
.GetLine(iPrev
);
370 int lineStartPosn
= styler
.LineStart(lineStart
);
373 // Find the first word after some whitespace
374 int firstWordPosn
= skipWhitespace(lineStartPosn
, iPrev
, styler
);
375 if (firstWordPosn
>= iPrev
) {
376 // Have something like {^ <<}
377 //XXX Look at the first previous non-comment non-white line
378 // to establish the context. Not too likely though.
381 switch (prevStyle
= styler
.StyleAt(firstWordPosn
)) {
383 case SCE_RB_WORD_DEMOTED
:
384 case SCE_RB_IDENTIFIER
:
390 int firstWordEndPosn
= firstWordPosn
;
391 char *dst
= prevWord
;
393 if (firstWordEndPosn
>= iPrev
||
394 styler
.StyleAt(firstWordEndPosn
) != prevStyle
) {
398 *dst
++ = styler
[firstWordEndPosn
];
399 firstWordEndPosn
+= 1;
401 //XXX Write a style-aware thing to regex scintilla buffer objects
402 if (!strcmp(prevWord
, "undef")
403 || !strcmp(prevWord
, "def")
404 || !strcmp(prevWord
, "alias")) {
405 // These keywords are what we were looking for
411 // Routine that saves us from allocating a buffer for the here-doc target
412 // targetEndPos points one past the end of the current target
413 static bool haveTargetMatch(int currPos
,
418 if (lengthDoc
- currPos
< targetEndPos
- targetStartPos
) {
422 for (i
= targetStartPos
, j
= currPos
;
423 i
< targetEndPos
&& j
< lengthDoc
;
425 if (styler
[i
] != styler
[j
]) {
432 // We need a check because the form
433 // [identifier] <<[target]
434 // is ambiguous. The Ruby lexer/parser resolves it by
435 // looking to see if [identifier] names a variable or a
436 // function. If it's the first, it's the start of a here-doc.
437 // If it's a var, it's an operator. This lexer doesn't
438 // maintain a symbol table, so it looks ahead to see what's
439 // going on, in cases where we have
440 // ^[white-space]*[identifier([.|::]identifier)*][white-space]*<<[target]
442 // If there's no occurrence of [target] on a line, assume we don't.
444 // return true == yes, we have no heredocs
446 static bool sureThisIsNotHeredoc(int lt2StartPos
,
449 // Use full document, not just part we're styling
450 int lengthDoc
= styler
.Length();
451 int lineStart
= styler
.GetLine(lt2StartPos
);
452 int lineStartPosn
= styler
.LineStart(lineStart
);
454 const bool definitely_not_a_here_doc
= true;
455 const bool looks_like_a_here_doc
= false;
457 // Find the first word after some whitespace
458 int firstWordPosn
= skipWhitespace(lineStartPosn
, lt2StartPos
, styler
);
459 if (firstWordPosn
>= lt2StartPos
) {
460 return definitely_not_a_here_doc
;
462 prevStyle
= styler
.StyleAt(firstWordPosn
);
463 // If we have '<<' following a keyword, it's not a heredoc
464 if (prevStyle
!= SCE_RB_IDENTIFIER
) {
465 return definitely_not_a_here_doc
;
467 int newStyle
= prevStyle
;
468 // Some compilers incorrectly warn about uninit newStyle
469 for (firstWordPosn
+= 1; firstWordPosn
<= lt2StartPos
; firstWordPosn
+= 1) {
470 // Inner loop looks at the name
471 for (; firstWordPosn
<= lt2StartPos
; firstWordPosn
+= 1) {
472 newStyle
= styler
.StyleAt(firstWordPosn
);
473 if (newStyle
!= prevStyle
) {
477 // Do we have '::' or '.'?
478 if (firstWordPosn
< lt2StartPos
&& newStyle
== SCE_RB_OPERATOR
) {
479 char ch
= styler
[firstWordPosn
];
482 } else if (ch
== ':') {
483 if (styler
.StyleAt(++firstWordPosn
) != SCE_RB_OPERATOR
) {
484 return definitely_not_a_here_doc
;
485 } else if (styler
[firstWordPosn
] != ':') {
486 return definitely_not_a_here_doc
;
495 // Skip next batch of white-space
496 firstWordPosn
= skipWhitespace(firstWordPosn
, lt2StartPos
, styler
);
497 if (firstWordPosn
!= lt2StartPos
) {
498 // Have [[^ws[identifier]ws[*something_else*]ws<<
499 return definitely_not_a_here_doc
;
501 // OK, now 'j' will point to the current spot moving ahead
502 int j
= firstWordPosn
+ 1;
503 if (styler
.StyleAt(j
) != SCE_RB_OPERATOR
|| styler
[j
] != '<') {
504 // This shouldn't happen
505 return definitely_not_a_here_doc
;
507 int nextLineStartPosn
= styler
.LineStart(lineStart
+ 1);
508 if (nextLineStartPosn
>= lengthDoc
) {
509 return definitely_not_a_here_doc
;
511 j
= skipWhitespace(j
+ 1, nextLineStartPosn
, styler
);
512 if (j
>= lengthDoc
) {
513 return definitely_not_a_here_doc
;
516 int target_start
, target_end
;
517 // From this point on no more styling, since we're looking ahead
518 if (styler
[j
] == '-') {
522 allow_indent
= false;
525 // Allow for quoted targets.
526 char target_quote
= 0;
531 target_quote
= styler
[j
];
535 if (isSafeAlnum(styler
[j
])) {
536 // Init target_end because some compilers think it won't
537 // be initialized by the time it's used
538 target_start
= target_end
= j
;
541 return definitely_not_a_here_doc
;
543 for (; j
< lengthDoc
; j
++) {
544 if (!isSafeAlnum(styler
[j
])) {
545 if (target_quote
&& styler
[j
] != target_quote
) {
547 return definitely_not_a_here_doc
;
550 // And for now make sure that it's a newline
551 // don't handle arbitrary expressions yet
555 // Now we can move to the character after the string delimiter.
558 j
= skipWhitespace(j
, lengthDoc
, styler
);
559 if (j
>= lengthDoc
) {
560 return definitely_not_a_here_doc
;
563 if (ch
== '#' || isEOLChar(ch
)) {
564 // This is OK, so break and continue;
567 return definitely_not_a_here_doc
;
573 // Just look at the start of each line
574 int last_line
= styler
.GetLine(lengthDoc
- 1);
575 // But don't go too far
576 if (last_line
> lineStart
+ 50) {
577 last_line
= lineStart
+ 50;
579 for (int line_num
= lineStart
+ 1; line_num
<= last_line
; line_num
++) {
581 j
= skipWhitespace(styler
.LineStart(line_num
), lengthDoc
, styler
);
583 j
= styler
.LineStart(line_num
);
585 // target_end is one past the end
586 if (haveTargetMatch(j
, lengthDoc
, target_start
, target_end
, styler
)) {
588 return looks_like_a_here_doc
;
591 return definitely_not_a_here_doc
;
594 //todo: if we aren't looking at a stdio character,
595 // move to the start of the first line that is not in a
596 // multi-line construct
598 static void synchronizeDocStart(unsigned int& startPos
,
602 bool skipWhiteSpace
=false) {
605 int style
= actual_style(styler
.StyleAt(startPos
));
610 // Don't do anything else with these.
615 // Quick way to characterize each line
617 for (lineStart
= styler
.GetLine(pos
); lineStart
> 0; lineStart
--) {
618 // Now look at the style before the previous line's EOL
619 pos
= styler
.LineStart(lineStart
) - 1;
624 char ch
= styler
.SafeGetCharAt(pos
);
625 char chPrev
= styler
.SafeGetCharAt(pos
- 1);
626 if (ch
== '\n' && chPrev
== '\r') {
629 if (styler
.SafeGetCharAt(pos
- 1) == '\\') {
630 // Continuation line -- keep going
631 } else if (actual_style(styler
.StyleAt(pos
)) != SCE_RB_DEFAULT
) {
632 // Part of multi-line construct -- keep going
633 } else if (currLineContainsHereDelims(pos
, styler
)) {
634 // Keep going, with pos and length now pointing
635 // at the end of the here-doc delimiter
636 } else if (skipWhiteSpace
&& isEmptyLine(pos
, styler
)) {
642 pos
= styler
.LineStart(lineStart
);
643 length
+= (startPos
- pos
);
645 initStyle
= SCE_RB_DEFAULT
;
648 static void ColouriseRbDoc(unsigned int startPos
, int length
, int initStyle
,
649 WordList
*keywordlists
[], Accessor
&styler
) {
651 // Lexer for Ruby often has to backtrack to start of current style to determine
652 // which characters are being used as quotes, how deeply nested is the
653 // start position and what the termination string is for here documents
655 WordList
&keywords
= *keywordlists
[0];
661 // 0: '<<' encountered
662 // 1: collect the delimiter
663 // 1b: text between the end of the delimiter and the EOL
664 // 2: here doc text (lines after the delimiter)
665 char Quote
; // the char after '<<'
666 bool Quoted
; // true if Quote in ('\'','"','`')
667 int DelimiterLength
; // strlen(Delimiter)
668 char Delimiter
[256]; // the Delimiter, limit of 256: from Perl
674 CanBeIndented
= false;
681 int numDots
= 0; // For numbers --
682 // Don't start lexing in the middle of a num
684 synchronizeDocStart(startPos
, length
, initStyle
, styler
, // ref args
687 bool preferRE
= true;
688 int state
= initStyle
;
689 int lengthDoc
= startPos
+ length
;
691 char prevWord
[MAX_KEYWORD_LENGTH
+ 1]; // 1 byte for zero
696 char chPrev
= styler
.SafeGetCharAt(startPos
- 1);
697 char chNext
= styler
.SafeGetCharAt(startPos
);
698 bool is_real_number
= true; // Differentiate between constants and ?-sequences.
699 // Ruby uses a different mask because bad indentation is marked by oring with 32
700 styler
.StartAt(startPos
, 127);
701 styler
.StartSegment(startPos
);
703 static int q_states
[] = {SCE_RB_STRING_Q
,
709 static const char* q_chars
= "qQrwWx";
711 // In most cases a value of 2 should be ample for the code in the
712 // Ruby library, and the code the user is likely to enter.
714 // fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}"
715 // if options[:verbose]
716 // from fileutils.rb nests to a level of 2
717 // If the user actually hits a 6th occurrence of '#{' in a double-quoted
718 // string (including regex'es, %Q, %<sym>, %w, and other strings
719 // that interpolate), it will stay as a string. The problem with this
720 // is that quotes might flip, a 7th '#{' will look like a comment,
721 // and code-folding might be wrong.
723 // If anyone runs into this problem, I recommend raising this
724 // value slightly higher to replacing the fixed array with a linked
725 // list. Keep in mind this code will be called everytime the lexer
728 #define INNER_STRINGS_MAX_COUNT 5
729 // These vars track our instances of "...#{,,,%Q<..#{,,,}...>,,,}..."
730 int inner_string_types
[INNER_STRINGS_MAX_COUNT
];
731 // Track # braces when we push a new #{ thing
732 int inner_expn_brace_counts
[INNER_STRINGS_MAX_COUNT
];
733 QuoteCls inner_quotes
[INNER_STRINGS_MAX_COUNT
];
734 int inner_string_count
= 0;
735 int brace_counts
= 0; // Number of #{ ... } things within an expression
738 for (i
= 0; i
< INNER_STRINGS_MAX_COUNT
; i
++) {
739 inner_string_types
[i
] = 0;
740 inner_expn_brace_counts
[i
] = 0;
742 for (i
= startPos
; i
< lengthDoc
; i
++) {
744 chNext
= styler
.SafeGetCharAt(i
+ 1);
745 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
747 if (styler
.IsLeadByte(ch
)) {
754 // skip on DOS/Windows
755 //No, don't, because some things will get tagged on,
756 // so we won't recognize keywords, for example
758 if (ch
== '\r' && chNext
== '\n') {
763 if (HereDoc
.State
== 1 && isEOLChar(ch
)) {
764 // Begin of here-doc (the line after the here-doc delimiter):
766 styler
.ColourTo(i
-1, state
);
767 // Don't check for a missing quote, just jump into
768 // the here-doc state
769 state
= SCE_RB_HERE_Q
;
772 // Regular transitions
773 if (state
== SCE_RB_DEFAULT
) {
774 if (isSafeDigit(ch
)) {
775 styler
.ColourTo(i
- 1, state
);
776 state
= SCE_RB_NUMBER
;
777 is_real_number
= true;
779 } else if (isHighBitChar(ch
) || iswordstart(ch
)) {
780 styler
.ColourTo(i
- 1, state
);
782 } else if (ch
== '#') {
783 styler
.ColourTo(i
- 1, state
);
784 state
= SCE_RB_COMMENTLINE
;
785 } else if (ch
== '=') {
786 // =begin indicates the start of a comment (doc) block
787 if (i
== 0 || (isEOLChar(chPrev
)
789 && styler
.SafeGetCharAt(i
+ 2) == 'e'
790 && styler
.SafeGetCharAt(i
+ 3) == 'g'
791 && styler
.SafeGetCharAt(i
+ 4) == 'i'
792 && styler
.SafeGetCharAt(i
+ 5) == 'n'
793 && !isSafeWordcharOrHigh(styler
.SafeGetCharAt(i
+ 6)))) {
794 styler
.ColourTo(i
- 1, state
);
797 styler
.ColourTo(i
- 1, state
);
798 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
801 } else if (ch
== '"') {
802 styler
.ColourTo(i
- 1, state
);
803 state
= SCE_RB_STRING
;
806 } else if (ch
== '\'') {
807 styler
.ColourTo(i
- 1, state
);
808 state
= SCE_RB_CHARACTER
;
811 } else if (ch
== '`') {
812 styler
.ColourTo(i
- 1, state
);
813 state
= SCE_RB_BACKTICKS
;
816 } else if (ch
== '@') {
817 // Instance or class var
818 styler
.ColourTo(i
- 1, state
);
820 state
= SCE_RB_CLASS_VAR
;
821 advance_char(i
, ch
, chNext
, chNext2
); // pass by ref
823 state
= SCE_RB_INSTANCE_VAR
;
825 } else if (ch
== '$') {
826 // Check for a builtin global
827 styler
.ColourTo(i
- 1, state
);
828 // Recognize it bit by bit
829 state
= SCE_RB_GLOBAL
;
830 } else if (ch
== '/' && preferRE
) {
832 styler
.ColourTo(i
- 1, state
);
833 state
= SCE_RB_REGEX
;
836 } else if (ch
== '<' && chNext
== '<' && chNext2
!= '=') {
838 // Recognise the '<<' symbol - either a here document or a binary op
839 styler
.ColourTo(i
- 1, state
);
842 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
844 if (! (strchr("\"\'`_-", chNext2
) || isSafeAlpha(chNext2
))) {
845 // It's definitely not a here-doc,
846 // based on Ruby's lexer/parser in the
847 // heredoc_identifier routine.
848 // Nothing else to do.
849 } else if (preferRE
) {
850 if (sureThisIsHeredoc(i
- 1, styler
, prevWord
)) {
851 state
= SCE_RB_HERE_DELIM
;
854 // else leave it in default state
856 if (sureThisIsNotHeredoc(i
- 1, styler
)) {
857 // leave state as default
858 // We don't have all the heuristics Perl has for indications
859 // of a here-doc, because '<<' is overloadable and used
860 // for so many other classes.
862 state
= SCE_RB_HERE_DELIM
;
866 preferRE
= (state
!= SCE_RB_HERE_DELIM
);
867 } else if (ch
== ':') {
868 styler
.ColourTo(i
- 1, state
);
870 // Mark "::" as an operator, not symbol start
871 styler
.ColourTo(i
+ 1, SCE_RB_OPERATOR
);
872 advance_char(i
, ch
, chNext
, chNext2
); // pass by ref
873 state
= SCE_RB_DEFAULT
;
875 } else if (isSafeWordcharOrHigh(chNext
)) {
876 state
= SCE_RB_SYMBOL
;
877 } else if (strchr("[*!~+-*/%=<>&^|", chNext
)) {
878 // Do the operator analysis in-line, looking ahead
879 // Based on the table in pickaxe 2nd ed., page 339
880 bool doColoring
= true;
883 if (chNext2
== ']' ) {
884 char ch_tmp
= styler
.SafeGetCharAt(i
+ 3);
888 chNext
= styler
.SafeGetCharAt(i
+ 1);
900 if (chNext2
== '*') {
903 chNext
= styler
.SafeGetCharAt(i
+ 1);
905 advance_char(i
, ch
, chNext
, chNext2
);
910 if (chNext2
== '=' || chNext2
== '~') {
913 chNext
= styler
.SafeGetCharAt(i
+ 1);
915 advance_char(i
, ch
, chNext
, chNext2
);
920 if (chNext2
== '<') {
923 chNext
= styler
.SafeGetCharAt(i
+ 1);
924 } else if (chNext2
== '=') {
925 char ch_tmp
= styler
.SafeGetCharAt(i
+ 3);
926 if (ch_tmp
== '>') { // <=> operator
929 chNext
= styler
.SafeGetCharAt(i
+ 1);
936 advance_char(i
, ch
, chNext
, chNext2
);
941 // Simple one-character operators
942 advance_char(i
, ch
, chNext
, chNext2
);
946 styler
.ColourTo(i
, SCE_RB_SYMBOL
);
947 state
= SCE_RB_DEFAULT
;
949 } else if (!preferRE
) {
950 // Don't color symbol strings (yet)
951 // Just color the ":" and color rest as string
952 styler
.ColourTo(i
, SCE_RB_SYMBOL
);
953 state
= SCE_RB_DEFAULT
;
955 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
956 state
= SCE_RB_DEFAULT
;
959 } else if (ch
== '%') {
960 styler
.ColourTo(i
- 1, state
);
961 bool have_string
= false;
962 if (strchr(q_chars
, chNext
) && !isSafeWordcharOrHigh(chNext2
)) {
964 const char *hit
= strchr(q_chars
, chNext
);
966 state
= q_states
[hit
- q_chars
];
970 chNext
= styler
.SafeGetCharAt(i
+ 1);
973 } else if (preferRE
&& !isSafeWordcharOrHigh(chNext
)) {
974 // Ruby doesn't allow high bit chars here,
975 // but the editor host might
976 state
= SCE_RB_STRING_QQ
;
978 advance_char(i
, ch
, chNext
, chNext2
); // pass by ref
982 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
986 } else if (ch
== '?') {
987 styler
.ColourTo(i
- 1, state
);
988 if (iswhitespace(chNext
) || chNext
== '\n' || chNext
== '\r') {
989 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
991 // It's the start of a character code escape sequence
992 // Color it as a number.
993 state
= SCE_RB_NUMBER
;
994 is_real_number
= false;
996 } else if (isoperator(ch
) || ch
== '.') {
997 styler
.ColourTo(i
- 1, state
);
998 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
999 // If we're ending an expression or block,
1000 // assume it ends an object, and the ambivalent
1001 // constructs are binary operators
1003 // So if we don't have one of these chars,
1004 // we aren't ending an object exp'n, and ops
1005 // like : << / are unary operators.
1010 } else if (ch
== '}' && --brace_counts
< 0
1011 && inner_string_count
> 0) {
1012 styler
.ColourTo(i
, SCE_RB_OPERATOR
);
1013 exitInnerExpression(inner_string_types
,
1014 inner_expn_brace_counts
,
1017 state
, brace_counts
, Quote
);
1019 preferRE
= (strchr(")}].", ch
) == NULL
);
1021 // Stay in default state
1022 } else if (isEOLChar(ch
)) {
1023 // Make sure it's a true line-end, with no backslash
1024 if ((ch
== '\r' || (ch
== '\n' && chPrev
!= '\r'))
1025 && chPrev
!= '\\') {
1026 // Assume we've hit the end of the statement.
1030 } else if (state
== SCE_RB_WORD
) {
1031 if (ch
== '.' || !isSafeWordcharOrHigh(ch
)) {
1032 // Words include x? in all contexts,
1033 // and <letters>= after either 'def' or a dot
1034 // Move along until a complete word is on our left
1036 // Default accessor treats '.' as word-chars,
1037 // but we don't for now.
1040 && isSafeWordcharOrHigh(chPrev
)
1042 || strchr(" \t\n\r", chNext
) != NULL
)
1043 && (!strcmp(prevWord
, "def")
1044 || followsDot(styler
.GetStartSegment(), styler
))) {
1045 // <name>= is a name only when being def'd -- Get it the next time
1046 // This means that <name>=<name> is always lexed as
1047 // <name>, (op, =), <name>
1048 } else if ((ch
== '?' || ch
== '!')
1049 && isSafeWordcharOrHigh(chPrev
)
1050 && !isSafeWordcharOrHigh(chNext
)) {
1051 // <name>? is a name -- Get it the next time
1052 // But <name>?<name> is always lexed as
1053 // <name>, (op, ?), <name>
1054 // Same with <name>! to indicate a method that
1055 // modifies its target
1056 } else if (isEOLChar(ch
)
1057 && isMatch(styler
, lengthDoc
, i
- 7, "__END__")) {
1058 styler
.ColourTo(i
, SCE_RB_DATASECTION
);
1059 state
= SCE_RB_DATASECTION
;
1060 // No need to handle this state -- we'll just move to the end
1063 int wordStartPos
= styler
.GetStartSegment();
1064 int word_style
= ClassifyWordRb(wordStartPos
, i
- 1, keywords
, styler
, prevWord
);
1065 switch (word_style
) {
1067 preferRE
= RE_CanFollowKeyword(prevWord
);
1070 case SCE_RB_WORD_DEMOTED
:
1074 case SCE_RB_IDENTIFIER
:
1075 if (isMatch(styler
, lengthDoc
, wordStartPos
, "print")) {
1077 } else if (isEOLChar(ch
)) {
1087 // We might be redefining an operator-method
1090 // And if it's the first
1091 redo_char(i
, ch
, chNext
, chNext2
, state
); // pass by ref
1094 } else if (state
== SCE_RB_NUMBER
) {
1095 if (!is_real_number
) {
1097 styler
.ColourTo(i
, state
);
1098 state
= SCE_RB_DEFAULT
;
1100 } else if (strchr("\\ntrfvaebs", chNext
)) {
1101 // Terminal escape sequence -- handle it next time
1102 // Nothing more to do this time through the loop
1103 } else if (chNext
== 'C' || chNext
== 'M') {
1104 if (chNext2
!= '-') {
1105 // \C or \M ends the sequence -- handle it next time
1107 // Move from abc?\C-x
1113 chNext
= styler
.SafeGetCharAt(i
+ 1);
1115 } else if (chNext
== 'c') {
1116 // Stay here, \c is a combining sequence
1117 advance_char(i
, ch
, chNext
, chNext2
); // pass by ref
1119 // ?\x, including ?\\ is final.
1120 styler
.ColourTo(i
+ 1, state
);
1121 state
= SCE_RB_DEFAULT
;
1123 advance_char(i
, ch
, chNext
, chNext2
);
1125 } else if (isSafeAlnumOrHigh(ch
) || ch
== '_') {
1127 } else if (ch
== '.' && ++numDots
== 1) {
1130 styler
.ColourTo(i
- 1, state
);
1131 redo_char(i
, ch
, chNext
, chNext2
, state
); // pass by ref
1134 } else if (state
== SCE_RB_COMMENTLINE
) {
1135 if (isEOLChar(ch
)) {
1136 styler
.ColourTo(i
- 1, state
);
1137 state
= SCE_RB_DEFAULT
;
1138 // Use whatever setting we had going into the comment
1140 } else if (state
== SCE_RB_HERE_DELIM
) {
1141 // See the comment for SCE_RB_HERE_DELIM in LexPerl.cxx
1142 // Slightly different: if we find an immediate '-',
1143 // the target can appear indented.
1145 if (HereDoc
.State
== 0) { // '<<' encountered
1147 HereDoc
.DelimiterLength
= 0;
1149 HereDoc
.CanBeIndented
= true;
1150 advance_char(i
, ch
, chNext
, chNext2
); // pass by ref
1152 HereDoc
.CanBeIndented
= false;
1154 if (isEOLChar(ch
)) {
1155 // Bail out of doing a here doc if there's no target
1156 state
= SCE_RB_DEFAULT
;
1161 if (ch
== '\'' || ch
== '"' || ch
== '`') {
1162 HereDoc
.Quoted
= true;
1163 HereDoc
.Delimiter
[0] = '\0';
1165 HereDoc
.Quoted
= false;
1166 HereDoc
.Delimiter
[0] = ch
;
1167 HereDoc
.Delimiter
[1] = '\0';
1168 HereDoc
.DelimiterLength
= 1;
1171 } else if (HereDoc
.State
== 1) { // collect the delimiter
1172 if (isEOLChar(ch
)) {
1173 // End the quote now, and go back for more
1174 styler
.ColourTo(i
- 1, state
);
1175 state
= SCE_RB_DEFAULT
;
1180 } else if (HereDoc
.Quoted
) {
1181 if (ch
== HereDoc
.Quote
) { // closing quote => end of delimiter
1182 styler
.ColourTo(i
, state
);
1183 state
= SCE_RB_DEFAULT
;
1186 if (ch
== '\\' && !isEOLChar(chNext
)) {
1187 advance_char(i
, ch
, chNext
, chNext2
);
1189 HereDoc
.Delimiter
[HereDoc
.DelimiterLength
++] = ch
;
1190 HereDoc
.Delimiter
[HereDoc
.DelimiterLength
] = '\0';
1192 } else { // an unquoted here-doc delimiter
1193 if (isSafeAlnumOrHigh(ch
) || ch
== '_') {
1194 HereDoc
.Delimiter
[HereDoc
.DelimiterLength
++] = ch
;
1195 HereDoc
.Delimiter
[HereDoc
.DelimiterLength
] = '\0';
1197 styler
.ColourTo(i
- 1, state
);
1198 redo_char(i
, ch
, chNext
, chNext2
, state
);
1202 if (HereDoc
.DelimiterLength
>= static_cast<int>(sizeof(HereDoc
.Delimiter
)) - 1) {
1203 styler
.ColourTo(i
- 1, state
);
1204 state
= SCE_RB_ERROR
;
1208 } else if (state
== SCE_RB_HERE_Q
) {
1209 // Not needed: HereDoc.State == 2
1210 // Indentable here docs: look backwards
1211 // Non-indentable: look forwards, like in Perl
1213 // Why: so we can quickly resolve things like <<-" abc"
1215 if (!HereDoc
.CanBeIndented
) {
1216 if (isEOLChar(chPrev
)
1217 && isMatch(styler
, lengthDoc
, i
, HereDoc
.Delimiter
)) {
1218 styler
.ColourTo(i
- 1, state
);
1219 i
+= HereDoc
.DelimiterLength
- 1;
1220 chNext
= styler
.SafeGetCharAt(i
+ 1);
1221 if (isEOLChar(chNext
)) {
1222 styler
.ColourTo(i
, SCE_RB_HERE_DELIM
);
1223 state
= SCE_RB_DEFAULT
;
1227 // Otherwise we skipped through the here doc faster.
1229 } else if (isEOLChar(chNext
)
1230 && lookingAtHereDocDelim(styler
,
1231 i
- HereDoc
.DelimiterLength
+ 1,
1233 HereDoc
.Delimiter
)) {
1234 styler
.ColourTo(i
- 1 - HereDoc
.DelimiterLength
, state
);
1235 styler
.ColourTo(i
, SCE_RB_HERE_DELIM
);
1236 state
= SCE_RB_DEFAULT
;
1240 } else if (state
== SCE_RB_CLASS_VAR
1241 || state
== SCE_RB_INSTANCE_VAR
1242 || state
== SCE_RB_SYMBOL
) {
1243 if (!isSafeWordcharOrHigh(ch
)) {
1244 styler
.ColourTo(i
- 1, state
);
1245 redo_char(i
, ch
, chNext
, chNext2
, state
); // pass by ref
1248 } else if (state
== SCE_RB_GLOBAL
) {
1249 if (!isSafeWordcharOrHigh(ch
)) {
1250 // handle special globals here as well
1251 if (chPrev
== '$') {
1253 // Include the next char, like $-a
1254 advance_char(i
, ch
, chNext
, chNext2
);
1256 styler
.ColourTo(i
, state
);
1257 state
= SCE_RB_DEFAULT
;
1259 styler
.ColourTo(i
- 1, state
);
1260 redo_char(i
, ch
, chNext
, chNext2
, state
); // pass by ref
1264 } else if (state
== SCE_RB_POD
) {
1265 // PODs end with ^=end\s, -- any whitespace can follow =end
1266 if (strchr(" \t\n\r", ch
) != NULL
1268 && isEOLChar(styler
[i
- 5])
1269 && isMatch(styler
, lengthDoc
, i
- 4, "=end")) {
1270 styler
.ColourTo(i
- 1, state
);
1271 state
= SCE_RB_DEFAULT
;
1274 } else if (state
== SCE_RB_REGEX
|| state
== SCE_RB_STRING_QR
) {
1275 if (ch
== '\\' && Quote
.Up
!= '\\') {
1277 advance_char(i
, ch
, chNext
, chNext2
);
1278 } else if (ch
== Quote
.Down
) {
1280 if (Quote
.Count
== 0) {
1281 // Include the options
1282 while (isSafeAlpha(chNext
)) {
1285 chNext
= styler
.SafeGetCharAt(i
+ 1);
1287 styler
.ColourTo(i
, state
);
1288 state
= SCE_RB_DEFAULT
;
1291 } else if (ch
== Quote
.Up
) {
1292 // Only if close quoter != open quoter
1295 } else if (ch
== '#' ) {
1297 && inner_string_count
< INNER_STRINGS_MAX_COUNT
) {
1299 styler
.ColourTo(i
- 1, state
);
1300 styler
.ColourTo(i
+ 1, SCE_RB_OPERATOR
);
1301 enterInnerExpression(inner_string_types
,
1302 inner_expn_brace_counts
,
1310 advance_char(i
, ch
, chNext
, chNext2
);
1312 //todo: distinguish comments from pound chars
1313 // for now, handle as comment
1314 styler
.ColourTo(i
- 1, state
);
1315 bool inEscape
= false;
1316 while (++i
< lengthDoc
) {
1317 ch
= styler
.SafeGetCharAt(i
);
1320 } else if (isEOLChar(ch
)) {
1321 // Comment inside a regex
1322 styler
.ColourTo(i
- 1, SCE_RB_COMMENTLINE
);
1324 } else if (inEscape
) {
1325 inEscape
= false; // don't look at char
1326 } else if (ch
== Quote
.Down
) {
1327 // Have the regular handler deal with this
1328 // to get trailing modifiers.
1334 chNext
= styler
.SafeGetCharAt(i
+ 1);
1335 chNext2
= styler
.SafeGetCharAt(i
+ 2);
1338 // Quotes of all kinds...
1339 } else if (state
== SCE_RB_STRING_Q
|| state
== SCE_RB_STRING_QQ
||
1340 state
== SCE_RB_STRING_QX
|| state
== SCE_RB_STRING_QW
||
1341 state
== SCE_RB_STRING
|| state
== SCE_RB_CHARACTER
||
1342 state
== SCE_RB_BACKTICKS
) {
1343 if (!Quote
.Down
&& !isspacechar(ch
)) {
1345 } else if (ch
== '\\' && Quote
.Up
!= '\\') {
1346 //Riddle me this: Is it safe to skip *every* escaped char?
1347 advance_char(i
, ch
, chNext
, chNext2
);
1348 } else if (ch
== Quote
.Down
) {
1350 if (Quote
.Count
== 0) {
1351 styler
.ColourTo(i
, state
);
1352 state
= SCE_RB_DEFAULT
;
1355 } else if (ch
== Quote
.Up
) {
1357 } else if (ch
== '#' && chNext
== '{'
1358 && inner_string_count
< INNER_STRINGS_MAX_COUNT
1359 && state
!= SCE_RB_CHARACTER
1360 && state
!= SCE_RB_STRING_Q
) {
1362 styler
.ColourTo(i
- 1, state
);
1363 styler
.ColourTo(i
+ 1, SCE_RB_OPERATOR
);
1364 enterInnerExpression(inner_string_types
,
1365 inner_expn_brace_counts
,
1373 advance_char(i
, ch
, chNext
, chNext2
);
1377 if (state
== SCE_RB_ERROR
) {
1382 if (state
== SCE_RB_WORD
) {
1383 // We've ended on a word, possibly at EOF, and need to
1385 (void) ClassifyWordRb(styler
.GetStartSegment(), lengthDoc
- 1, keywords
, styler
, prevWord
);
1387 styler
.ColourTo(lengthDoc
- 1, state
);
1391 // Helper functions for folding, disambiguation keywords
1392 // Assert that there are no high-bit chars
1394 static void getPrevWord(int pos
,
1401 for (i
= pos
- 1; i
> 0; i
--) {
1402 if (actual_style(styler
.StyleAt(i
)) != word_state
) {
1407 if (i
< pos
- MAX_KEYWORD_LENGTH
) // overflow
1408 i
= pos
- MAX_KEYWORD_LENGTH
;
1409 char *dst
= prevWord
;
1410 for (; i
<= pos
; i
++) {
1416 static bool keywordIsAmbiguous(const char *prevWord
)
1418 // Order from most likely used to least likely
1419 // Lots of ways to do a loop in Ruby besides 'while/until'
1420 if (!strcmp(prevWord
, "if")
1421 || !strcmp(prevWord
, "do")
1422 || !strcmp(prevWord
, "while")
1423 || !strcmp(prevWord
, "unless")
1424 || !strcmp(prevWord
, "until")) {
1431 // Demote keywords in the following conditions:
1432 // if, while, unless, until modify a statement
1433 // do after a while or until, as a noise word (like then after if)
1435 static bool keywordIsModifier(const char *word
,
1439 if (word
[0] == 'd' && word
[1] == 'o' && !word
[2]) {
1440 return keywordDoStartsLoop(pos
, styler
);
1443 int style
= SCE_RB_DEFAULT
;
1444 int lineStart
= styler
.GetLine(pos
);
1445 int lineStartPosn
= styler
.LineStart(lineStart
);
1447 while (--pos
>= lineStartPosn
) {
1448 style
= actual_style(styler
.StyleAt(pos
));
1449 if (style
== SCE_RB_DEFAULT
) {
1450 if (iswhitespace(ch
= styler
[pos
])) {
1452 } else if (ch
== '\r' || ch
== '\n') {
1453 // Scintilla's LineStart() and GetLine() routines aren't
1454 // platform-independent, so if we have text prepared with
1455 // a different system we can't rely on it.
1462 if (pos
< lineStartPosn
) {
1463 return false; //XXX not quite right if the prev line is a continuation
1465 // First things where the action is unambiguous
1467 case SCE_RB_DEFAULT
:
1468 case SCE_RB_COMMENTLINE
:
1470 case SCE_RB_CLASSNAME
:
1471 case SCE_RB_DEFNAME
:
1472 case SCE_RB_MODULE_NAME
:
1474 case SCE_RB_OPERATOR
:
1477 // Watch out for uses of 'else if'
1478 //XXX: Make a list of other keywords where 'if' isn't a modifier
1479 // and can appear legitimately
1480 // Formulate this to avoid warnings from most compilers
1481 if (strcmp(word
, "if") == 0) {
1482 char prevWord
[MAX_KEYWORD_LENGTH
+ 1];
1483 getPrevWord(pos
, prevWord
, styler
, SCE_RB_WORD
);
1484 return strcmp(prevWord
, "else") != 0;
1490 // Assume that if the keyword follows an operator,
1491 // usually it's a block assignment, like
1492 // a << if x then y else z
1505 #define WHILE_BACKWARDS "elihw"
1506 #define UNTIL_BACKWARDS "litnu"
1508 // Nothing fancy -- look to see if we follow a while/until somewhere
1509 // on the current line
1511 static bool keywordDoStartsLoop(int pos
,
1516 int lineStart
= styler
.GetLine(pos
);
1517 int lineStartPosn
= styler
.LineStart(lineStart
);
1519 while (--pos
>= lineStartPosn
) {
1520 style
= actual_style(styler
.StyleAt(pos
));
1521 if (style
== SCE_RB_DEFAULT
) {
1522 if ((ch
= styler
[pos
]) == '\r' || ch
== '\n') {
1523 // Scintilla's LineStart() and GetLine() routines aren't
1524 // platform-independent, so if we have text prepared with
1525 // a different system we can't rely on it.
1528 } else if (style
== SCE_RB_WORD
) {
1529 // Check for while or until, but write the word in backwards
1530 char prevWord
[MAX_KEYWORD_LENGTH
+ 1]; // 1 byte for zero
1531 char *dst
= prevWord
;
1534 for (start_word
= pos
;
1535 start_word
>= lineStartPosn
&& actual_style(styler
.StyleAt(start_word
)) == SCE_RB_WORD
;
1537 if (++wordLen
< MAX_KEYWORD_LENGTH
) {
1538 *dst
++ = styler
[start_word
];
1542 // Did we see our keyword?
1543 if (!strcmp(prevWord
, WHILE_BACKWARDS
)
1544 || !strcmp(prevWord
, UNTIL_BACKWARDS
)) {
1547 // We can move pos to the beginning of the keyword, and then
1548 // accept another decrement, as we can never have two contiguous
1552 // <- move to start_word
1554 // <- loop decrement
1555 // ^ # pointing to end of word1 is fine
1565 * The language is quite complex to analyze without a full parse.
1566 * For example, this line shouldn't affect fold level:
1568 * print "hello" if feeling_friendly?
1570 * Neither should this:
1573 * if feeling_friendly?
1578 * if feeling_friendly? #++
1583 * So we cheat, by actually looking at the existing indentation
1584 * levels for each line, and just echoing it back. Like Python.
1585 * Then if we get better at it, we'll take braces into consideration,
1586 * which always affect folding levels.
1588 * How the keywords should work:
1590 * __FILE__ __LINE__ BEGIN END alias and
1591 * defined? false in nil not or self super then
1595 * begin class def do for module when {
1600 * Increment if these start a statement
1601 * if unless until while -- do nothing if they're modifiers
1603 * These end a block if there's no modifier, but don't bother
1604 * break next redo retry return yield
1606 * These temporarily de-indent, but re-indent
1607 * case else elsif ensure rescue
1609 * This means that the folder reflects indentation rather
1610 * than setting it. The language-service updates indentation
1611 * when users type return and finishes entering de-denters.
1613 * Later offer to fold POD, here-docs, strings, and blocks of comments
1616 static void FoldRbDoc(unsigned int startPos
, int length
, int initStyle
,
1617 WordList
*[], Accessor
&styler
) {
1618 const bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
1619 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
1621 synchronizeDocStart(startPos
, length
, initStyle
, styler
, // ref args
1623 unsigned int endPos
= startPos
+ length
;
1624 int visibleChars
= 0;
1625 int lineCurrent
= styler
.GetLine(startPos
);
1626 int levelPrev
= startPos
== 0 ? 0 : (styler
.LevelAt(lineCurrent
)
1627 & SC_FOLDLEVELNUMBERMASK
1628 & ~SC_FOLDLEVELBASE
);
1629 int levelCurrent
= levelPrev
;
1630 char chNext
= styler
[startPos
];
1631 int styleNext
= styler
.StyleAt(startPos
);
1632 int stylePrev
= startPos
<= 1 ? SCE_RB_DEFAULT
: styler
.StyleAt(startPos
- 1);
1633 bool buffer_ends_with_eol
= false;
1634 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
1636 chNext
= styler
.SafeGetCharAt(i
+ 1);
1637 int style
= styleNext
;
1638 styleNext
= styler
.StyleAt(i
+ 1);
1639 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
1640 if (style
== SCE_RB_COMMENTLINE
) {
1641 if (foldComment
&& stylePrev
!= SCE_RB_COMMENTLINE
) {
1642 if (chNext
== '{') {
1644 } else if (chNext
== '}' && levelCurrent
> 0) {
1648 } else if (style
== SCE_RB_OPERATOR
) {
1649 if (strchr("[{(", ch
)) {
1651 } else if (strchr(")}]", ch
)) {
1652 // Don't decrement below 0
1653 if (levelCurrent
> 0)
1656 } else if (style
== SCE_RB_WORD
&& styleNext
!= SCE_RB_WORD
) {
1657 // Look at the keyword on the left and decide what to do
1658 char prevWord
[MAX_KEYWORD_LENGTH
+ 1]; // 1 byte for zero
1660 getPrevWord(i
, prevWord
, styler
, SCE_RB_WORD
);
1661 if (!strcmp(prevWord
, "end")) {
1662 // Don't decrement below 0
1663 if (levelCurrent
> 0)
1665 } else if ( !strcmp(prevWord
, "if")
1666 || !strcmp(prevWord
, "def")
1667 || !strcmp(prevWord
, "class")
1668 || !strcmp(prevWord
, "module")
1669 || !strcmp(prevWord
, "begin")
1670 || !strcmp(prevWord
, "case")
1671 || !strcmp(prevWord
, "do")
1672 || !strcmp(prevWord
, "while")
1673 || !strcmp(prevWord
, "unless")
1674 || !strcmp(prevWord
, "until")
1675 || !strcmp(prevWord
, "for")
1681 int lev
= levelPrev
;
1682 if (visibleChars
== 0 && foldCompact
)
1683 lev
|= SC_FOLDLEVELWHITEFLAG
;
1684 if ((levelCurrent
> levelPrev
) && (visibleChars
> 0))
1685 lev
|= SC_FOLDLEVELHEADERFLAG
;
1686 styler
.SetLevel(lineCurrent
, lev
|SC_FOLDLEVELBASE
);
1688 levelPrev
= levelCurrent
;
1690 buffer_ends_with_eol
= true;
1691 } else if (!isspacechar(ch
)) {
1693 buffer_ends_with_eol
= false;
1697 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
1698 if (!buffer_ends_with_eol
) {
1700 int new_lev
= levelCurrent
;
1701 if (visibleChars
== 0 && foldCompact
)
1702 new_lev
|= SC_FOLDLEVELWHITEFLAG
;
1703 if ((levelCurrent
> levelPrev
) && (visibleChars
> 0))
1704 new_lev
|= SC_FOLDLEVELHEADERFLAG
;
1705 levelCurrent
= new_lev
;
1707 styler
.SetLevel(lineCurrent
, levelCurrent
|SC_FOLDLEVELBASE
);
1710 static const char * const rubyWordListDesc
[] = {
1715 LexerModule
lmRuby(SCLEX_RUBY
, ColouriseRbDoc
, "ruby", FoldRbDoc
, rubyWordListDesc
);