in ext/scintilla/lexers/LexRuby.cxx [695:1485]
static void ColouriseRbDoc(Sci_PositionU startPos, Sci_Position length, int initStyle,
WordList *keywordlists[], Accessor &styler) {
// Lexer for Ruby often has to backtrack to start of current style to determine
// which characters are being used as quotes, how deeply nested is the
// start position and what the termination string is for here documents
WordList &keywords = *keywordlists[0];
class HereDocCls {
public:
int State;
// States
// 0: '<<' encountered
// 1: collect the delimiter
// 1b: text between the end of the delimiter and the EOL
// 2: here doc text (lines after the delimiter)
char Quote; // the char after '<<'
bool Quoted; // true if Quote in ('\'','"','`')
int DelimiterLength; // strlen(Delimiter)
char Delimiter[256]; // the Delimiter, limit of 256: from Perl
bool CanBeIndented;
HereDocCls() {
State = 0;
DelimiterLength = 0;
Delimiter[0] = '\0';
CanBeIndented = false;
}
};
HereDocCls HereDoc;
QuoteCls Quote;
int numDots = 0; // For numbers --
// Don't start lexing in the middle of a num
synchronizeDocStart(startPos, length, initStyle, styler, // ref args
false);
bool preferRE = true;
int state = initStyle;
Sci_Position lengthDoc = startPos + length;
char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
prevWord[0] = '\0';
if (length == 0)
return;
char chPrev = styler.SafeGetCharAt(startPos - 1);
char chNext = styler.SafeGetCharAt(startPos);
bool is_real_number = true; // Differentiate between constants and ?-sequences.
styler.StartAt(startPos);
styler.StartSegment(startPos);
static int q_states[] = {SCE_RB_STRING_Q,
SCE_RB_STRING_QQ,
SCE_RB_STRING_QR,
SCE_RB_STRING_QW,
SCE_RB_STRING_QW,
SCE_RB_STRING_QX
};
static const char *q_chars = "qQrwWx";
// In most cases a value of 2 should be ample for the code in the
// Ruby library, and the code the user is likely to enter.
// For example,
// fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}"
// if options[:verbose]
// from fileutils.rb nests to a level of 2
// If the user actually hits a 6th occurrence of '#{' in a double-quoted
// string (including regex'es, %Q, %<sym>, %w, and other strings
// that interpolate), it will stay as a string. The problem with this
// is that quotes might flip, a 7th '#{' will look like a comment,
// and code-folding might be wrong.
// If anyone runs into this problem, I recommend raising this
// value slightly higher to replacing the fixed array with a linked
// list. Keep in mind this code will be called every time the lexer
// is invoked.
#define INNER_STRINGS_MAX_COUNT 5
// These vars track our instances of "...#{,,,%Q<..#{,,,}...>,,,}..."
int inner_string_types[INNER_STRINGS_MAX_COUNT];
// Track # braces when we push a new #{ thing
int inner_expn_brace_counts[INNER_STRINGS_MAX_COUNT];
QuoteCls inner_quotes[INNER_STRINGS_MAX_COUNT];
int inner_string_count = 0;
int brace_counts = 0; // Number of #{ ... } things within an expression
Sci_Position i;
for (i = 0; i < INNER_STRINGS_MAX_COUNT; i++) {
inner_string_types[i] = 0;
inner_expn_brace_counts[i] = 0;
}
for (i = startPos; i < lengthDoc; i++) {
char ch = chNext;
chNext = styler.SafeGetCharAt(i + 1);
char chNext2 = styler.SafeGetCharAt(i + 2);
if (styler.IsLeadByte(ch)) {
chNext = chNext2;
chPrev = ' ';
i += 1;
continue;
}
// skip on DOS/Windows
//No, don't, because some things will get tagged on,
// so we won't recognize keywords, for example
#if 0
if (ch == '\r' && chNext == '\n') {
continue;
}
#endif
if (HereDoc.State == 1 && isEOLChar(ch)) {
// Begin of here-doc (the line after the here-doc delimiter):
HereDoc.State = 2;
styler.ColourTo(i-1, state);
// Don't check for a missing quote, just jump into
// the here-doc state
state = SCE_RB_HERE_Q;
}
// Regular transitions
if (state == SCE_RB_DEFAULT) {
if (isSafeDigit(ch)) {
styler.ColourTo(i - 1, state);
state = SCE_RB_NUMBER;
is_real_number = true;
numDots = 0;
} else if (isHighBitChar(ch) || iswordstart(ch)) {
styler.ColourTo(i - 1, state);
state = SCE_RB_WORD;
} else if (ch == '#') {
styler.ColourTo(i - 1, state);
state = SCE_RB_COMMENTLINE;
} else if (ch == '=') {
// =begin indicates the start of a comment (doc) block
if ((i == 0 || isEOLChar(chPrev))
&& chNext == 'b'
&& styler.SafeGetCharAt(i + 2) == 'e'
&& styler.SafeGetCharAt(i + 3) == 'g'
&& styler.SafeGetCharAt(i + 4) == 'i'
&& styler.SafeGetCharAt(i + 5) == 'n'
&& !isSafeWordcharOrHigh(styler.SafeGetCharAt(i + 6))) {
styler.ColourTo(i - 1, state);
state = SCE_RB_POD;
} else {
styler.ColourTo(i - 1, state);
styler.ColourTo(i, SCE_RB_OPERATOR);
preferRE = true;
}
} else if (ch == '"') {
styler.ColourTo(i - 1, state);
state = SCE_RB_STRING;
Quote.New();
Quote.Open(ch);
} else if (ch == '\'') {
styler.ColourTo(i - 1, state);
state = SCE_RB_CHARACTER;
Quote.New();
Quote.Open(ch);
} else if (ch == '`') {
styler.ColourTo(i - 1, state);
state = SCE_RB_BACKTICKS;
Quote.New();
Quote.Open(ch);
} else if (ch == '@') {
// Instance or class var
styler.ColourTo(i - 1, state);
if (chNext == '@') {
state = SCE_RB_CLASS_VAR;
advance_char(i, ch, chNext, chNext2); // pass by ref
} else {
state = SCE_RB_INSTANCE_VAR;
}
} else if (ch == '$') {
// Check for a builtin global
styler.ColourTo(i - 1, state);
// Recognize it bit by bit
state = SCE_RB_GLOBAL;
} else if (ch == '/' && preferRE) {
// Ambigous operator
styler.ColourTo(i - 1, state);
state = SCE_RB_REGEX;
Quote.New();
Quote.Open(ch);
} else if (ch == '<' && chNext == '<' && chNext2 != '=') {
// Recognise the '<<' symbol - either a here document or a binary op
styler.ColourTo(i - 1, state);
i++;
chNext = chNext2;
styler.ColourTo(i, SCE_RB_OPERATOR);
if (!(strchr("\"\'`_-", chNext2) || isSafeAlpha(chNext2))) {
// It's definitely not a here-doc,
// based on Ruby's lexer/parser in the
// heredoc_identifier routine.
// Nothing else to do.
} else if (preferRE) {
if (sureThisIsHeredoc(i - 1, styler, prevWord)) {
state = SCE_RB_HERE_DELIM;
HereDoc.State = 0;
}
// else leave it in default state
} else {
if (sureThisIsNotHeredoc(i - 1, styler)) {
// leave state as default
// We don't have all the heuristics Perl has for indications
// of a here-doc, because '<<' is overloadable and used
// for so many other classes.
} else {
state = SCE_RB_HERE_DELIM;
HereDoc.State = 0;
}
}
preferRE = (state != SCE_RB_HERE_DELIM);
} else if (ch == ':') {
styler.ColourTo(i - 1, state);
if (chNext == ':') {
// Mark "::" as an operator, not symbol start
styler.ColourTo(i + 1, SCE_RB_OPERATOR);
advance_char(i, ch, chNext, chNext2); // pass by ref
state = SCE_RB_DEFAULT;
preferRE = false;
} else if (isSafeWordcharOrHigh(chNext)) {
state = SCE_RB_SYMBOL;
} else if ((chNext == '@' || chNext == '$') &&
isSafeWordcharOrHigh(chNext2)) {
// instance and global variable followed by an identifier
advance_char(i, ch, chNext, chNext2);
state = SCE_RB_SYMBOL;
} else if (((chNext == '@' && chNext2 == '@') ||
(chNext == '$' && chNext2 == '-')) &&
isSafeWordcharOrHigh(styler.SafeGetCharAt(i+3))) {
// class variables and special global variable "$-IDENTCHAR"
state = SCE_RB_SYMBOL;
// $-IDENTCHAR doesn't continue past the IDENTCHAR
if (chNext == '$') {
styler.ColourTo(i+3, SCE_RB_SYMBOL);
state = SCE_RB_DEFAULT;
}
i += 3;
ch = styler.SafeGetCharAt(i);
chNext = styler.SafeGetCharAt(i+1);
} else if (chNext == '$' && strchr("_~*$?!@/\\;,.=:<>\"&`'+", chNext2)) {
// single-character special global variables
i += 2;
ch = chNext2;
chNext = styler.SafeGetCharAt(i+1);
styler.ColourTo(i, SCE_RB_SYMBOL);
state = SCE_RB_DEFAULT;
} else if (strchr("[*!~+-*/%=<>&^|", chNext)) {
// Do the operator analysis in-line, looking ahead
// Based on the table in pickaxe 2nd ed., page 339
bool doColoring = true;
switch (chNext) {
case '[':
if (chNext2 == ']') {
char ch_tmp = styler.SafeGetCharAt(i + 3);
if (ch_tmp == '=') {
i += 3;
ch = ch_tmp;
chNext = styler.SafeGetCharAt(i + 1);
} else {
i += 2;
ch = chNext2;
chNext = ch_tmp;
}
} else {
doColoring = false;
}
break;
case '*':
if (chNext2 == '*') {
i += 2;
ch = chNext2;
chNext = styler.SafeGetCharAt(i + 1);
} else {
advance_char(i, ch, chNext, chNext2);
}
break;
case '!':
if (chNext2 == '=' || chNext2 == '~') {
i += 2;
ch = chNext2;
chNext = styler.SafeGetCharAt(i + 1);
} else {
advance_char(i, ch, chNext, chNext2);
}
break;
case '<':
if (chNext2 == '<') {
i += 2;
ch = chNext2;
chNext = styler.SafeGetCharAt(i + 1);
} else if (chNext2 == '=') {
char ch_tmp = styler.SafeGetCharAt(i + 3);
if (ch_tmp == '>') { // <=> operator
i += 3;
ch = ch_tmp;
chNext = styler.SafeGetCharAt(i + 1);
} else {
i += 2;
ch = chNext2;
chNext = ch_tmp;
}
} else {
advance_char(i, ch, chNext, chNext2);
}
break;
default:
// Simple one-character operators
advance_char(i, ch, chNext, chNext2);
break;
}
if (doColoring) {
styler.ColourTo(i, SCE_RB_SYMBOL);
state = SCE_RB_DEFAULT;
}
} else if (!preferRE) {
// Don't color symbol strings (yet)
// Just color the ":" and color rest as string
styler.ColourTo(i, SCE_RB_SYMBOL);
state = SCE_RB_DEFAULT;
} else {
styler.ColourTo(i, SCE_RB_OPERATOR);
state = SCE_RB_DEFAULT;
preferRE = true;
}
} else if (ch == '%') {
styler.ColourTo(i - 1, state);
bool have_string = false;
if (strchr(q_chars, chNext) && !isSafeWordcharOrHigh(chNext2)) {
Quote.New();
const char *hit = strchr(q_chars, chNext);
if (hit != NULL) {
state = q_states[hit - q_chars];
Quote.Open(chNext2);
i += 2;
ch = chNext2;
chNext = styler.SafeGetCharAt(i + 1);
have_string = true;
}
} else if (preferRE && !isSafeWordcharOrHigh(chNext)) {
// Ruby doesn't allow high bit chars here,
// but the editor host might
Quote.New();
state = SCE_RB_STRING_QQ;
Quote.Open(chNext);
advance_char(i, ch, chNext, chNext2); // pass by ref
have_string = true;
} else if (!isSafeWordcharOrHigh(chNext) && !iswhitespace(chNext) && !isEOLChar(chNext)) {
// Ruby doesn't allow high bit chars here,
// but the editor host might
Quote.New();
state = SCE_RB_STRING_QQ;
Quote.Open(chNext);
advance_char(i, ch, chNext, chNext2); // pass by ref
have_string = true;
}
if (!have_string) {
styler.ColourTo(i, SCE_RB_OPERATOR);
// stay in default
preferRE = true;
}
} else if (ch == '?') {
styler.ColourTo(i - 1, state);
if (iswhitespace(chNext) || chNext == '\n' || chNext == '\r') {
styler.ColourTo(i, SCE_RB_OPERATOR);
} else {
// It's the start of a character code escape sequence
// Color it as a number.
state = SCE_RB_NUMBER;
is_real_number = false;
}
} else if (isoperator(ch) || ch == '.') {
styler.ColourTo(i - 1, state);
styler.ColourTo(i, SCE_RB_OPERATOR);
// If we're ending an expression or block,
// assume it ends an object, and the ambivalent
// constructs are binary operators
//
// So if we don't have one of these chars,
// we aren't ending an object exp'n, and ops
// like : << / are unary operators.
if (ch == '{') {
++brace_counts;
preferRE = true;
} else if (ch == '}' && --brace_counts < 0
&& inner_string_count > 0) {
styler.ColourTo(i, SCE_RB_OPERATOR);
exitInnerExpression(inner_string_types,
inner_expn_brace_counts,
inner_quotes,
inner_string_count,
state, brace_counts, Quote);
} else {
preferRE = (strchr(")}].", ch) == NULL);
}
// Stay in default state
} else if (isEOLChar(ch)) {
// Make sure it's a true line-end, with no backslash
if ((ch == '\r' || (ch == '\n' && chPrev != '\r'))
&& chPrev != '\\') {
// Assume we've hit the end of the statement.
preferRE = true;
}
}
} else if (state == SCE_RB_WORD) {
if (ch == '.' || !isSafeWordcharOrHigh(ch)) {
// Words include x? in all contexts,
// and <letters>= after either 'def' or a dot
// Move along until a complete word is on our left
// Default accessor treats '.' as word-chars,
// but we don't for now.
if (ch == '='
&& isSafeWordcharOrHigh(chPrev)
&& (chNext == '('
|| strchr(" \t\n\r", chNext) != NULL)
&& (!strcmp(prevWord, "def")
|| followsDot(styler.GetStartSegment(), styler))) {
// <name>= is a name only when being def'd -- Get it the next time
// This means that <name>=<name> is always lexed as
// <name>, (op, =), <name>
} else if (ch == ':'
&& isSafeWordcharOrHigh(chPrev)
&& strchr(" \t\n\r", chNext) != NULL) {
state = SCE_RB_SYMBOL;
} else if ((ch == '?' || ch == '!')
&& isSafeWordcharOrHigh(chPrev)
&& !isSafeWordcharOrHigh(chNext)) {
// <name>? is a name -- Get it the next time
// But <name>?<name> is always lexed as
// <name>, (op, ?), <name>
// Same with <name>! to indicate a method that
// modifies its target
} else if (isEOLChar(ch)
&& isMatch(styler, lengthDoc, i - 7, "__END__")) {
styler.ColourTo(i, SCE_RB_DATASECTION);
state = SCE_RB_DATASECTION;
// No need to handle this state -- we'll just move to the end
preferRE = false;
} else {
Sci_Position wordStartPos = styler.GetStartSegment();
int word_style = ClassifyWordRb(wordStartPos, i - 1, keywords, styler, prevWord);
switch (word_style) {
case SCE_RB_WORD:
preferRE = RE_CanFollowKeyword(prevWord);
break;
case SCE_RB_WORD_DEMOTED:
preferRE = true;
break;
case SCE_RB_IDENTIFIER:
if (isMatch(styler, lengthDoc, wordStartPos, "print")) {
preferRE = true;
} else if (isEOLChar(ch)) {
preferRE = true;
} else {
preferRE = false;
}
break;
default:
preferRE = false;
}
if (ch == '.') {
// We might be redefining an operator-method
preferRE = false;
}
// And if it's the first
redo_char(i, ch, chNext, chNext2, state); // pass by ref
}
}
} else if (state == SCE_RB_NUMBER) {
if (!is_real_number) {
if (ch != '\\') {
styler.ColourTo(i, state);
state = SCE_RB_DEFAULT;
preferRE = false;
} else if (strchr("\\ntrfvaebs", chNext)) {
// Terminal escape sequence -- handle it next time
// Nothing more to do this time through the loop
} else if (chNext == 'C' || chNext == 'M') {
if (chNext2 != '-') {
// \C or \M ends the sequence -- handle it next time
} else {
// Move from abc?\C-x
// ^
// to
// ^
i += 2;
ch = chNext2;
chNext = styler.SafeGetCharAt(i + 1);
}
} else if (chNext == 'c') {
// Stay here, \c is a combining sequence
advance_char(i, ch, chNext, chNext2); // pass by ref
} else {
// ?\x, including ?\\ is final.
styler.ColourTo(i + 1, state);
state = SCE_RB_DEFAULT;
preferRE = false;
advance_char(i, ch, chNext, chNext2);
}
} else if (isSafeAlnumOrHigh(ch) || ch == '_') {
// Keep going
} else if (ch == '.' && chNext == '.') {
++numDots;
styler.ColourTo(i - 1, state);
redo_char(i, ch, chNext, chNext2, state); // pass by ref
} else if (ch == '.' && ++numDots == 1) {
// Keep going
} else {
styler.ColourTo(i - 1, state);
redo_char(i, ch, chNext, chNext2, state); // pass by ref
preferRE = false;
}
} else if (state == SCE_RB_COMMENTLINE) {
if (isEOLChar(ch)) {
styler.ColourTo(i - 1, state);
state = SCE_RB_DEFAULT;
// Use whatever setting we had going into the comment
}
} else if (state == SCE_RB_HERE_DELIM) {
// See the comment for SCE_RB_HERE_DELIM in LexPerl.cxx
// Slightly different: if we find an immediate '-',
// the target can appear indented.
if (HereDoc.State == 0) { // '<<' encountered
HereDoc.State = 1;
HereDoc.DelimiterLength = 0;
if (ch == '-') {
HereDoc.CanBeIndented = true;
advance_char(i, ch, chNext, chNext2); // pass by ref
} else {
HereDoc.CanBeIndented = false;
}
if (isEOLChar(ch)) {
// Bail out of doing a here doc if there's no target
state = SCE_RB_DEFAULT;
preferRE = false;
} else {
HereDoc.Quote = ch;
if (ch == '\'' || ch == '"' || ch == '`') {
HereDoc.Quoted = true;
HereDoc.Delimiter[0] = '\0';
} else {
HereDoc.Quoted = false;
HereDoc.Delimiter[0] = ch;
HereDoc.Delimiter[1] = '\0';
HereDoc.DelimiterLength = 1;
}
}
} else if (HereDoc.State == 1) { // collect the delimiter
if (isEOLChar(ch)) {
// End the quote now, and go back for more
styler.ColourTo(i - 1, state);
state = SCE_RB_DEFAULT;
i--;
chNext = ch;
preferRE = false;
} else if (HereDoc.Quoted) {
if (ch == HereDoc.Quote) { // closing quote => end of delimiter
styler.ColourTo(i, state);
state = SCE_RB_DEFAULT;
preferRE = false;
} else {
if (ch == '\\' && !isEOLChar(chNext)) {
advance_char(i, ch, chNext, chNext2);
}
HereDoc.Delimiter[HereDoc.DelimiterLength++] = ch;
HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
}
} else { // an unquoted here-doc delimiter
if (isSafeAlnumOrHigh(ch) || ch == '_') {
HereDoc.Delimiter[HereDoc.DelimiterLength++] = ch;
HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
} else {
styler.ColourTo(i - 1, state);
redo_char(i, ch, chNext, chNext2, state);
preferRE = false;
}
}
if (HereDoc.DelimiterLength >= static_cast<int>(sizeof(HereDoc.Delimiter)) - 1) {
styler.ColourTo(i - 1, state);
state = SCE_RB_ERROR;
preferRE = false;
}
}
} else if (state == SCE_RB_HERE_Q) {
// Not needed: HereDoc.State == 2
// Indentable here docs: look backwards
// Non-indentable: look forwards, like in Perl
//
// Why: so we can quickly resolve things like <<-" abc"
if (!HereDoc.CanBeIndented) {
if (isEOLChar(chPrev)
&& isMatch(styler, lengthDoc, i, HereDoc.Delimiter)) {
styler.ColourTo(i - 1, state);
i += HereDoc.DelimiterLength - 1;
chNext = styler.SafeGetCharAt(i + 1);
if (isEOLChar(chNext)) {
styler.ColourTo(i, SCE_RB_HERE_DELIM);
state = SCE_RB_DEFAULT;
HereDoc.State = 0;
preferRE = false;
}
// Otherwise we skipped through the here doc faster.
}
} else if (isEOLChar(chNext)
&& lookingAtHereDocDelim(styler,
i - HereDoc.DelimiterLength + 1,
lengthDoc,
HereDoc.Delimiter)) {
styler.ColourTo(i - 1 - HereDoc.DelimiterLength, state);
styler.ColourTo(i, SCE_RB_HERE_DELIM);
state = SCE_RB_DEFAULT;
preferRE = false;
HereDoc.State = 0;
}
} else if (state == SCE_RB_CLASS_VAR
|| state == SCE_RB_INSTANCE_VAR
|| state == SCE_RB_SYMBOL) {
if (state == SCE_RB_SYMBOL &&
// FIDs suffices '?' and '!'
(((ch == '!' || ch == '?') && chNext != '=') ||
// identifier suffix '='
(ch == '=' && (chNext != '~' && chNext != '>' &&
(chNext != '=' || chNext2 == '>'))))) {
styler.ColourTo(i, state);
state = SCE_RB_DEFAULT;
preferRE = false;
} else if (!isSafeWordcharOrHigh(ch)) {
styler.ColourTo(i - 1, state);
redo_char(i, ch, chNext, chNext2, state); // pass by ref
preferRE = false;
}
} else if (state == SCE_RB_GLOBAL) {
if (!isSafeWordcharOrHigh(ch)) {
// handle special globals here as well
if (chPrev == '$') {
if (ch == '-') {
// Include the next char, like $-a
advance_char(i, ch, chNext, chNext2);
}
styler.ColourTo(i, state);
state = SCE_RB_DEFAULT;
} else {
styler.ColourTo(i - 1, state);
redo_char(i, ch, chNext, chNext2, state); // pass by ref
}
preferRE = false;
}
} else if (state == SCE_RB_POD) {
// PODs end with ^=end\s, -- any whitespace can follow =end
if (strchr(" \t\n\r", ch) != NULL
&& i > 5
&& isEOLChar(styler[i - 5])
&& isMatch(styler, lengthDoc, i - 4, "=end")) {
styler.ColourTo(i - 1, state);
state = SCE_RB_DEFAULT;
preferRE = false;
}
} else if (state == SCE_RB_REGEX || state == SCE_RB_STRING_QR) {
if (ch == '\\' && Quote.Up != '\\') {
// Skip one
advance_char(i, ch, chNext, chNext2);
} else if (ch == Quote.Down) {
Quote.Count--;
if (Quote.Count == 0) {
// Include the options
while (isSafeAlpha(chNext)) {
i++;
ch = chNext;
chNext = styler.SafeGetCharAt(i + 1);
}
styler.ColourTo(i, state);
state = SCE_RB_DEFAULT;
preferRE = false;
}
} else if (ch == Quote.Up) {
// Only if close quoter != open quoter
Quote.Count++;
} else if (ch == '#') {
if (chNext == '{'
&& inner_string_count < INNER_STRINGS_MAX_COUNT) {
// process #{ ... }
styler.ColourTo(i - 1, state);
styler.ColourTo(i + 1, SCE_RB_OPERATOR);
enterInnerExpression(inner_string_types,
inner_expn_brace_counts,
inner_quotes,
inner_string_count,
state,
brace_counts,
Quote);
preferRE = true;
// Skip one
advance_char(i, ch, chNext, chNext2);
} else {
//todo: distinguish comments from pound chars
// for now, handle as comment
styler.ColourTo(i - 1, state);
bool inEscape = false;
while (++i < lengthDoc) {
ch = styler.SafeGetCharAt(i);
if (ch == '\\') {
inEscape = true;
} else if (isEOLChar(ch)) {
// Comment inside a regex
styler.ColourTo(i - 1, SCE_RB_COMMENTLINE);
break;
} else if (inEscape) {
inEscape = false; // don't look at char
} else if (ch == Quote.Down) {
// Have the regular handler deal with this
// to get trailing modifiers.
i--;
ch = styler[i];
break;
}
}
chNext = styler.SafeGetCharAt(i + 1);
}
}
// Quotes of all kinds...
} else if (state == SCE_RB_STRING_Q || state == SCE_RB_STRING_QQ ||
state == SCE_RB_STRING_QX || state == SCE_RB_STRING_QW ||
state == SCE_RB_STRING || state == SCE_RB_CHARACTER ||
state == SCE_RB_BACKTICKS) {
if (!Quote.Down && !isspacechar(ch)) {
Quote.Open(ch);
} else if (ch == '\\' && Quote.Up != '\\') {
//Riddle me this: Is it safe to skip *every* escaped char?
advance_char(i, ch, chNext, chNext2);
} else if (ch == Quote.Down) {
Quote.Count--;
if (Quote.Count == 0) {
styler.ColourTo(i, state);
state = SCE_RB_DEFAULT;
preferRE = false;
}
} else if (ch == Quote.Up) {
Quote.Count++;
} else if (ch == '#' && chNext == '{'
&& inner_string_count < INNER_STRINGS_MAX_COUNT
&& state != SCE_RB_CHARACTER
&& state != SCE_RB_STRING_Q) {
// process #{ ... }
styler.ColourTo(i - 1, state);
styler.ColourTo(i + 1, SCE_RB_OPERATOR);
enterInnerExpression(inner_string_types,
inner_expn_brace_counts,
inner_quotes,
inner_string_count,
state,
brace_counts,
Quote);
preferRE = true;
// Skip one
advance_char(i, ch, chNext, chNext2);
}
}
if (state == SCE_RB_ERROR) {
break;
}
chPrev = ch;
}
if (state == SCE_RB_WORD) {
// We've ended on a word, possibly at EOF, and need to
// classify it.
(void) ClassifyWordRb(styler.GetStartSegment(), lengthDoc - 1, keywords, styler, prevWord);
} else {
styler.ColourTo(lengthDoc - 1, state);
}
}