aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/SciLexer.h35
-rw-r--r--include/Scintilla.iface38
-rw-r--r--src/LexRuby.cxx1423
3 files changed, 1229 insertions, 267 deletions
diff --git a/include/SciLexer.h b/include/SciLexer.h
index d6ec62a5c..3d8bd277e 100644
--- a/include/SciLexer.h
+++ b/include/SciLexer.h
@@ -250,6 +250,7 @@
#define SCE_PL_ARRAY 13
#define SCE_PL_HASH 14
#define SCE_PL_SYMBOLTABLE 15
+#define SCE_PL_VARIABLE_INDEXER 16
#define SCE_PL_REGEX 17
#define SCE_PL_REGSUBST 18
#define SCE_PL_LONGQUOTE 19
@@ -265,6 +266,40 @@
#define SCE_PL_STRING_QR 29
#define SCE_PL_STRING_QW 30
#define SCE_PL_POD_VERB 31
+#define SCE_RB_DEFAULT 0
+#define SCE_RB_ERROR 1
+#define SCE_RB_COMMENTLINE 2
+#define SCE_RB_POD 3
+#define SCE_RB_NUMBER 4
+#define SCE_RB_WORD 5
+#define SCE_RB_STRING 6
+#define SCE_RB_CHARACTER 7
+#define SCE_RB_CLASSNAME 8
+#define SCE_RB_DEFNAME 9
+#define SCE_RB_OPERATOR 10
+#define SCE_RB_IDENTIFIER 11
+#define SCE_RB_REGEX 12
+#define SCE_RB_GLOBAL 13
+#define SCE_RB_SYMBOL 14
+#define SCE_RB_MODULE_NAME 15
+#define SCE_RB_INSTANCE_VAR 16
+#define SCE_RB_CLASS_VAR 17
+#define SCE_RB_BACKTICKS 18
+#define SCE_RB_DATASECTION 19
+#define SCE_RB_HERE_DELIM 20
+#define SCE_RB_HERE_Q 21
+#define SCE_RB_HERE_QQ 22
+#define SCE_RB_HERE_QX 23
+#define SCE_RB_STRING_Q 24
+#define SCE_RB_STRING_QQ 25
+#define SCE_RB_STRING_QX 26
+#define SCE_RB_STRING_QR 27
+#define SCE_RB_STRING_QW 28
+#define SCE_RB_WORD_DEMOTED 29
+#define SCE_RB_STDIN 30
+#define SCE_RB_STDOUT 31
+#define SCE_RB_STDERR 40
+#define SCE_RB_UPPER_BOUND 41
#define SCE_B_DEFAULT 0
#define SCE_B_COMMENT 1
#define SCE_B_NUMBER 2
diff --git a/include/Scintilla.iface b/include/Scintilla.iface
index 2cc7414cc..b7a9b7d13 100644
--- a/include/Scintilla.iface
+++ b/include/Scintilla.iface
@@ -1809,7 +1809,6 @@ val SCLEX_FREEBASIC=75
val SCLEX_AUTOMATIC=1000
# Lexical states for SCLEX_PYTHON
lex Python=SCLEX_PYTHON SCE_P_
-lex Ruby=SCLEX_RUBY SCE_P_
val SCE_P_DEFAULT=0
val SCE_P_COMMENTLINE=1
val SCE_P_NUMBER=2
@@ -1995,6 +1994,7 @@ val SCE_PL_SCALAR=12
val SCE_PL_ARRAY=13
val SCE_PL_HASH=14
val SCE_PL_SYMBOLTABLE=15
+val SCE_PL_VARIABLE_INDEXER=16
val SCE_PL_REGEX=17
val SCE_PL_REGSUBST=18
val SCE_PL_LONGQUOTE=19
@@ -2010,6 +2010,42 @@ val SCE_PL_STRING_QX=28
val SCE_PL_STRING_QR=29
val SCE_PL_STRING_QW=30
val SCE_PL_POD_VERB=31
+# Lexical states for SCLEX_RUBY
+lex Ruby=SCLEX_RUBY SCE_RB_
+val SCE_RB_DEFAULT=0
+val SCE_RB_ERROR=1
+val SCE_RB_COMMENTLINE=2
+val SCE_RB_POD=3
+val SCE_RB_NUMBER=4
+val SCE_RB_WORD=5
+val SCE_RB_STRING=6
+val SCE_RB_CHARACTER=7
+val SCE_RB_CLASSNAME=8
+val SCE_RB_DEFNAME=9
+val SCE_RB_OPERATOR=10
+val SCE_RB_IDENTIFIER=11
+val SCE_RB_REGEX=12
+val SCE_RB_GLOBAL=13
+val SCE_RB_SYMBOL=14
+val SCE_RB_MODULE_NAME=15
+val SCE_RB_INSTANCE_VAR=16
+val SCE_RB_CLASS_VAR=17
+val SCE_RB_BACKTICKS=18
+val SCE_RB_DATASECTION=19
+val SCE_RB_HERE_DELIM=20
+val SCE_RB_HERE_Q=21
+val SCE_RB_HERE_QQ=22
+val SCE_RB_HERE_QX=23
+val SCE_RB_STRING_Q=24
+val SCE_RB_STRING_QQ=25
+val SCE_RB_STRING_QX=26
+val SCE_RB_STRING_QR=27
+val SCE_RB_STRING_QW=28
+val SCE_RB_WORD_DEMOTED=29
+val SCE_RB_STDIN=30
+val SCE_RB_STDOUT=31
+val SCE_RB_STDERR=40
+val SCE_RB_UPPER_BOUND=41
# Lexical states for SCLEX_VB, SCLEX_VBSCRIPT, SCLEX_POWERBASIC
lex VB=SCLEX_VB SCE_B_
lex VBScript=SCLEX_VBSCRIPT SCE_B_
diff --git a/src/LexRuby.cxx b/src/LexRuby.cxx
index 692a47ade..878071a53 100644
--- a/src/LexRuby.cxx
+++ b/src/LexRuby.cxx
@@ -19,342 +19,1233 @@
#include "Scintilla.h"
#include "SciLexer.h"
-static void ClassifyWordRb(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler, char *prevWord) {
+#ifdef SCI_NAMESPACE
+using namespace Scintilla;
+#endif
+
+//XXX Identical to Perl, put in common area
+static inline bool isEOLChar(char ch) {
+ return (ch == '\r') || (ch == '\n');
+}
+
+static inline bool isRubyOperatorChar(char ch) {
+ return strchr("%^&*\\()-+=|{}[]:;<>,/?!.~",ch) != NULL;
+}
+
+
+static inline bool isSafeAlpha(char ch) {
+ return ((unsigned int) ch <= 127) && isalpha(ch);
+}
+
+#define MAX_KEYWORD_LENGTH 200
+
+#define STYLE_MASK 63
+#define actual_style(style) (style & STYLE_MASK)
+
+static bool followsDot(unsigned int pos, Accessor &styler) {
+ styler.Flush();
+ for (; pos >= 1; --pos) {
+ int style = actual_style(styler.StyleAt(pos));
+ char ch;
+ switch (style) {
+ case SCE_RB_DEFAULT:
+ ch = styler[pos];
+ if (ch == ' ' || ch == '\t') {
+ //continue
+ } else {
+ return false;
+ }
+ break;
+
+ case SCE_RB_OPERATOR:
+ return styler[pos] == '.';
+
+ default:
+ return false;
+ }
+ }
+ return false;
+}
+
+// Forward declarations
+static bool keywordIsAmbiguous(const char *prevWord);
+static bool keywordDoStartsLoop(int pos,
+ Accessor &styler);
+static bool keywordIsModifier(const char *word,
+ int pos,
+ Accessor &styler);
+
+static int ClassifyWordRb(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler, char *prevWord) {
char s[100];
- bool wordIsNumber = isdigit(styler[start]) != 0;
- for (unsigned int i = 0; i < end - start + 1 && i < 30; i++) {
- s[i] = styler[start + i];
- s[i + 1] = '\0';
+ unsigned int i, j;
+ unsigned int lim = end - start + 1; // num chars to copy
+ if (lim >= MAX_KEYWORD_LENGTH) {
+ lim = MAX_KEYWORD_LENGTH - 1;
+ }
+ for (i = start, j = 0; j < lim; i++, j++) {
+ s[j] = styler[i];
}
- char chAttr = SCE_P_IDENTIFIER;
+ s[j] = '\0';
+ int chAttr;
if (0 == strcmp(prevWord, "class"))
- chAttr = SCE_P_CLASSNAME;
+ chAttr = SCE_RB_CLASSNAME;
else if (0 == strcmp(prevWord, "module"))
- chAttr = SCE_P_CLASSNAME;
+ chAttr = SCE_RB_MODULE_NAME;
else if (0 == strcmp(prevWord, "def"))
- chAttr = SCE_P_DEFNAME;
- else if (wordIsNumber)
- chAttr = SCE_P_NUMBER;
- else if (keywords.InList(s))
- chAttr = SCE_P_WORD;
- // make sure that dot-qualifiers inside the word are lexed correct
- else for (unsigned int i = 0; i < end - start + 1; i++) {
- if (styler[start + i] == '.') {
- styler.ColourTo(start + i - 1, chAttr);
- styler.ColourTo(start + i, SCE_P_OPERATOR);
+ chAttr = SCE_RB_DEFNAME;
+ else if (keywords.InList(s) && !followsDot(start - 1, styler)) {
+ if (keywordIsAmbiguous(s)
+ && keywordIsModifier(s, start, styler)) {
+
+ // Demoted keywords are colored as keywords,
+ // but do not affect changes in indentation.
+ //
+ // Consider the word 'if':
+ // 1. <<if test ...>> : normal
+ // 2. <<stmt if test>> : demoted
+ // 3. <<lhs = if ...>> : normal: start a new indent level
+ // 4. <<obj.if = 10>> : color as identifer, since it follows '.'
+
+ chAttr = SCE_RB_WORD_DEMOTED;
+ } else {
+ chAttr = SCE_RB_WORD;
+ }
+ } else
+ chAttr = SCE_RB_IDENTIFIER;
+ styler.ColourTo(end, chAttr);
+ if (chAttr == SCE_RB_WORD) {
+ strcpy(prevWord, s);
+ } else {
+ prevWord[0] = 0;
+ }
+ return chAttr;
+}
+
+
+//XXX Identical to Perl, put in common area
+static bool isMatch(Accessor &styler, int lengthDoc, int pos, const char *val) {
+ if ((pos + static_cast<int>(strlen(val))) >= lengthDoc) {
+ return false;
+ }
+ while (*val) {
+ if (*val != styler[pos++]) {
+ return false;
}
+ val++;
}
- styler.ColourTo(end, chAttr);
- strcpy(prevWord, s);
+ return true;
}
-static bool IsRbComment(Accessor &styler, int pos, int len) {
- return len>0 && styler[pos]=='#';
+// Do Ruby better -- find the end of the line, work back,
+// and then check for leading white space
+
+// Precondition: the here-doc target can be indented
+static bool lookingAtHereDocDelim(Accessor &styler,
+ int pos,
+ int lengthDoc,
+ const char *HereDocDelim)
+{
+ if (!isMatch(styler, lengthDoc, pos, HereDocDelim)) {
+ return false;
+ }
+ while (--pos > 0) {
+ char ch = styler[pos];
+ if (isEOLChar(ch)) {
+ return true;
+ } else if (ch != ' ' && ch != '\t') {
+ return false;
+ }
+ }
+ return false;
}
-static bool IsRbStringStart(char ch, char chNext, char chNext2) {
- if (ch == '\'' || ch == '"')
- return true;
- if (ch == 'u' || ch == 'U') {
- if (chNext == '"' || chNext == '\'')
- return true;
- if ((chNext == 'r' || chNext == 'R') && (chNext2 == '"' || chNext2 == '\''))
- return true;
- }
- if ((ch == 'r' || ch == 'R') && (chNext == '"' || chNext == '\''))
- return true;
+//XXX Identical to Perl, put in common area
+static char opposite(char ch) {
+ if (ch == '(')
+ return ')';
+ if (ch == '[')
+ return ']';
+ if (ch == '{')
+ return '}';
+ if (ch == '<')
+ return '>';
+ return ch;
+}
- return false;
+// Null transitions when we see we've reached the end
+// and need to relex the curr char.
+
+static void redo_char(int &i, char &ch, char &chNext, char &chNext2,
+ int &state) {
+ i--;
+ chNext2 = chNext;
+ chNext = ch;
+ state = SCE_RB_DEFAULT;
}
-static bool IsRbWordStart(char ch, char chNext, char chNext2) {
- return (iswordchar(ch) && !IsRbStringStart(ch, chNext, chNext2));
+static void advance_char(int &i, char &ch, char &chNext, char &chNext2) {
+ i++;
+ ch = chNext;
+ chNext = chNext2;
}
-/* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */
-static int GetRbStringState(Accessor &styler, int i, int *nextIndex) {
- char ch = styler.SafeGetCharAt(i);
- char chNext = styler.SafeGetCharAt(i + 1);
+// precondition: startPos points to one after the EOL char
+static bool currLineContainsHereDelims(int& startPos,
+ Accessor &styler) {
+ if (startPos <= 1)
+ return false;
- // Advance beyond r, u, or ur prefix, but bail if there are any unexpected chars
- if (ch == 'r' || ch == 'R') {
- i++;
- ch = styler.SafeGetCharAt(i);
- chNext = styler.SafeGetCharAt(i + 1);
- }
- else if (ch == 'u' || ch == 'U') {
- if (chNext == 'r' || chNext == 'R')
- i += 2;
- else
- i += 1;
- ch = styler.SafeGetCharAt(i);
- chNext = styler.SafeGetCharAt(i + 1);
- }
+ int pos;
+ for (pos = startPos - 1; pos > 0; pos--) {
+ char ch = styler.SafeGetCharAt(pos);
+ if (isEOLChar(ch)) {
+ // Leave the pointers where they are -- there are no
+ // here doc delims on the current line, even if
+ // the EOL isn't default style
+
+ return false;
+ } else {
+ styler.Flush();
+ if (actual_style(styler.StyleAt(pos)) == SCE_RB_HERE_DELIM) {
+ break;
+ }
+ }
+ }
+ if (pos == 0) {
+ return false;
+ }
+ // Update the pointers so we don't have to re-analyze the string
+ startPos = pos;
+ return true;
+}
- if (ch != '"' && ch != '\'') {
- *nextIndex = i + 1;
- return SCE_P_DEFAULT;
- }
- if (i>0 && styler.SafeGetCharAt(i-1) == '$') {
- *nextIndex = i + 1;
- return SCE_P_DEFAULT;
- }
+static bool isEmptyLine(int pos,
+ Accessor &styler) {
+ int spaceFlags = 0;
+ int lineCurrent = styler.GetLine(pos);
+ int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, NULL);
+ return (indentCurrent & SC_FOLDLEVELWHITEFLAG) != 0;
+}
- if (ch == chNext && ch == styler.SafeGetCharAt(i + 2)) {
- *nextIndex = i + 3;
+static bool RE_CanFollowKeyword(const char *keyword) {
+ if (!strcmp(keyword, "and")
+ || !strcmp(keyword, "begin")
+ || !strcmp(keyword, "break")
+ || !strcmp(keyword, "case")
+ || !strcmp(keyword, "do")
+ || !strcmp(keyword, "else")
+ || !strcmp(keyword, "elsif")
+ || !strcmp(keyword, "if")
+ || !strcmp(keyword, "next")
+ || !strcmp(keyword, "return")
+ || !strcmp(keyword, "when")
+ || !strcmp(keyword, "unless")
+ || !strcmp(keyword, "until")
+ || !strcmp(keyword, "not")
+ || !strcmp(keyword, "or")) {
+ return true;
+ }
+ return false;
+}
+
- if (ch == '"')
- return SCE_P_TRIPLEDOUBLE;
- else
- return SCE_P_TRIPLE;
- } else {
- *nextIndex = i + 1;
+//todo: if we aren't looking at a stdio character,
+// move to the start of the first line that is not in a
+// multi-line construct
- if (ch == '"')
- return SCE_P_STRING;
- else
- return SCE_P_CHARACTER;
- }
+static void synchronizeDocStart(unsigned int& startPos,
+ int &length,
+ int &initStyle,
+ Accessor &styler,
+ bool skipWhiteSpace=false) {
+
+ styler.Flush();
+ int style = actual_style(styler.StyleAt(startPos));
+ switch (style) {
+ case SCE_RB_STDIN:
+ case SCE_RB_STDOUT:
+ case SCE_RB_STDERR:
+ // Don't do anything else with these.
+ return;
+ }
+
+ int pos = startPos;
+ // Quick way to characterize each line
+ int lineStart;
+ for (lineStart = styler.GetLine(pos); lineStart > 0; lineStart--) {
+ // Now look at the style before the previous line's EOL
+ pos = styler.LineStart(lineStart) - 1;
+ if (pos <= 10) {
+ lineStart = 0;
+ break;
+ }
+ char ch = styler.SafeGetCharAt(pos);
+ char chPrev = styler.SafeGetCharAt(pos - 1);
+ if (ch == '\n' && chPrev == '\r') {
+ pos--;
+ }
+ if (styler.SafeGetCharAt(pos - 1) == '\\') {
+ // Continuation line -- keep going
+ } else if (actual_style(styler.StyleAt(pos)) != SCE_RB_DEFAULT) {
+ // Part of multi-line construct -- keep going
+ } else if (currLineContainsHereDelims(pos, styler)) {
+ // Keep going, with pos and length now pointing
+ // at the end of the here-doc delimiter
+ } else if (skipWhiteSpace && isEmptyLine(pos, styler)) {
+ // Keep going
+ } else {
+ break;
+ }
+ }
+ pos = styler.LineStart(lineStart);
+ length += (startPos - pos);
+ startPos = pos;
+ initStyle = SCE_RB_DEFAULT;
}
static void ColouriseRbDoc(unsigned int startPos, int length, int initStyle,
WordList *keywordlists[], Accessor &styler) {
- int lengthDoc = startPos + length;
+ // 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];
- // Backtrack to previous line in case need to fix its tab whinging
- if (startPos > 0) {
- int lineCurrent = styler.GetLine(startPos);
- if (lineCurrent > 0) {
- startPos = styler.LineStart(lineCurrent-1);
- if (startPos == 0)
- initStyle = SCE_P_DEFAULT;
- else
- initStyle = styler.StyleAt(startPos-1);
+ 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;
- // Ruby uses a different mask because bad indentation is marked by oring with 32
- styler.StartAt(startPos, 127);
+ class QuoteCls {
+ public:
+ int Count;
+ char Up;
+ char Down;
+ QuoteCls() {
+ this->New();
+ }
+ void New() {
+ Count = 0;
+ Up = '\0';
+ Down = '\0';
+ }
+ void Open(char u) {
+ Count++;
+ Up = u;
+ Down = opposite(Up);
+ }
+ };
+ QuoteCls Quote;
- WordList &keywords = *keywordlists[0];
+ int numDots = 0; // For numbers --
+ // Don't start lexing in the middle of a num
+
+ synchronizeDocStart(startPos, length, initStyle, styler, // ref args
+ false);
- int whingeLevel = styler.GetPropertyInt("tab.timmy.whinge.level");
- char prevWord[200];
+ bool preferRE = true;
+ int state = initStyle;
+ int lengthDoc = startPos + length;
+
+ char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
prevWord[0] = '\0';
if (length == 0)
- return ;
-
- int state = initStyle & 31;
+ return;
- int nextIndex = 0;
- char chPrev = ' ';
- char chPrev2 = ' ';
- char chNext = styler[startPos];
+ char chPrev = styler.SafeGetCharAt(startPos - 1);
+ char chNext = styler.SafeGetCharAt(startPos);
+ // Ruby uses a different mask because bad indentation is marked by oring with 32
+ styler.StartAt(startPos, 127);
styler.StartSegment(startPos);
- bool atStartLine = true;
- int spaceFlags = 0;
- for (int i = startPos; i < lengthDoc; i++) {
-
- if (atStartLine) {
- char chBad = static_cast<char>(64);
- char chGood = static_cast<char>(0);
- char chFlags = chGood;
- if (whingeLevel == 1) {
- chFlags = (spaceFlags & wsInconsistent) ? chBad : chGood;
- } else if (whingeLevel == 2) {
- chFlags = (spaceFlags & wsSpaceTab) ? chBad : chGood;
- } else if (whingeLevel == 3) {
- chFlags = (spaceFlags & wsSpace) ? chBad : chGood;
- } else if (whingeLevel == 4) {
- chFlags = (spaceFlags & wsTab) ? chBad : chGood;
- }
- styler.SetFlags(chFlags, static_cast<char>(state));
- atStartLine = false;
- }
+ 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";
+
+ for (int i = startPos; i < lengthDoc; i++) {
char ch = chNext;
chNext = styler.SafeGetCharAt(i + 1);
char chNext2 = styler.SafeGetCharAt(i + 2);
- if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == lengthDoc)) {
- if ((state == SCE_P_DEFAULT) || (state == SCE_P_TRIPLE) || (state == SCE_P_TRIPLEDOUBLE)) {
- // Perform colourisation of white space and triple quoted strings at end of each line to allow
- // tab marking to work inside white space and triple quoted strings
- styler.ColourTo(i, state);
- }
- atStartLine = true;
- }
-
- if (styler.IsLeadByte(ch)) {
- chNext = styler.SafeGetCharAt(i + 2);
+ if (styler.IsLeadByte(ch)) {
+ chNext = chNext2;
chPrev = ' ';
- chPrev2 = ' ';
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;
+ }
- if (state == SCE_P_STRINGEOL) {
- if (ch != '\r' && ch != '\n') {
- styler.ColourTo(i - 1, state);
- state = SCE_P_DEFAULT;
- }
- }
- if (state == SCE_P_DEFAULT) {
- if (IsRbWordStart(ch, chNext, chNext2)) {
- styler.ColourTo(i - 1, state);
- state = SCE_P_WORD;
+ // Regular transitions
+ if (state == SCE_RB_DEFAULT) {
+ if (isdigit(ch)) {
+ styler.ColourTo(i - 1, state);
+ state = SCE_RB_NUMBER;
+ numDots = 0;
+ } else if (iswordstart(ch)) {
+ styler.ColourTo(i - 1, state);
+ state = SCE_RB_WORD;
} else if (ch == '#') {
styler.ColourTo(i - 1, state);
- state = chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE;
- } else if (ch == '=' && chNext == 'b') {
+ state = SCE_RB_COMMENTLINE;
+ } else if (ch == '=') {
// =begin indicates the start of a comment (doc) block
- if(styler.SafeGetCharAt(i + 2) == 'e' && styler.SafeGetCharAt(i + 3) == 'g' && styler.SafeGetCharAt(i + 4) == 'i' && styler.SafeGetCharAt(i + 5) == 'n') {
+ 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'
+ && !iswordchar(styler.SafeGetCharAt(i + 6))) {
+ styler.ColourTo(i - 1, state);
+ state = SCE_RB_POD;
+ } else {
styler.ColourTo(i - 1, state);
- state = SCE_P_TRIPLEDOUBLE; //SCE_C_COMMENT;
+ styler.ColourTo(i, SCE_RB_OPERATOR);
+ preferRE = true;
}
- } else if (IsRbStringStart(ch, chNext, chNext2)) {
+ } else if (ch == '"') {
styler.ColourTo(i - 1, state);
- state = GetRbStringState(styler, i, &nextIndex);
- if (nextIndex != i + 1) {
- i = nextIndex - 1;
- ch = ' ';
- chPrev = ' ';
- chNext = styler.SafeGetCharAt(i + 1);
- }
- } else if (isoperator(ch)) {
+ 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);
- styler.ColourTo(i, SCE_P_OPERATOR);
- }
- } else if (state == SCE_P_WORD) {
- if (!iswordchar(ch)) {
- ClassifyWordRb(styler.GetStartSegment(), i - 1, keywords, styler, prevWord);
- state = SCE_P_DEFAULT;
- if (ch == '#') {
- state = chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE;
- } else if (IsRbStringStart(ch, chNext, chNext2)) {
- styler.ColourTo(i - 1, state);
- state = GetRbStringState(styler, i, &nextIndex);
- if (nextIndex != i + 1) {
- i = nextIndex - 1;
- ch = ' ';
- chPrev = ' ';
+ 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 (preferRE) {
+ state = SCE_RB_HERE_DELIM;
+ HereDoc.State = 0;
+ } else {
+ // 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.
+ preferRE = true;
+ }
+ } 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 (iswordchar(chNext)) {
+ state = SCE_RB_SYMBOL;
+ } 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) && !iswordchar(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 (!iswordchar(chNext)) {
+ 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 (isoperator(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.
+
+ 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 == '.' || !iswordchar(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 == '='
+ && iswordchar(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 == '?' || ch == '!')
+ && iswordchar(chPrev)
+ && !iswordchar(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 {
+ int 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;
+ }
+ redo_char(i, ch, chNext, chNext2, state); // pass by ref
+ }
+ }
+ } else if (state == SCE_RB_NUMBER) {
+ if (isalnum(ch) || ch == '_') {
+ // Keep going
+ } 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;
+ chNext2 = chNext;
+ 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 (isalnum(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;
}
- } else if (isoperator(ch)) {
- styler.ColourTo(i, SCE_P_OPERATOR);
- }
- }
- } else {
- if (state == SCE_P_COMMENTLINE || state == SCE_P_COMMENTBLOCK) {
- if (ch == '\r' || ch == '\n') {
+ }
+ if (HereDoc.DelimiterLength >= static_cast<int>(sizeof(HereDoc.Delimiter)) - 1) {
styler.ColourTo(i - 1, state);
- state = SCE_P_DEFAULT;
- }
- } else if (state == SCE_P_STRING) {
- if ((ch == '\r' || ch == '\n') && (chPrev != '\\')) {
- styler.ColourTo(i - 1, state);
- state = SCE_P_STRINGEOL;
- } else if (ch == '\\') {
- if (chNext == '\"' || chNext == '\'' || chNext == '\\') {
- i++;
- ch = chNext;
- chNext = styler.SafeGetCharAt(i + 1);
- }
- } else if (ch == '\"') {
- styler.ColourTo(i, state);
- state = SCE_P_DEFAULT;
+ state = SCE_RB_ERROR;
+ preferRE = false;
}
- } else if (state == SCE_P_CHARACTER) {
- if ((ch == '\r' || ch == '\n') && (chPrev != '\\')) {
- styler.ColourTo(i - 1, state);
- state = SCE_P_STRINGEOL;
- } else if (ch == '\\') {
- if (chNext == '\"' || chNext == '\'' || chNext == '\\') {
- i++;
+ }
+ } 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 (!iswordchar(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 (!iswordchar(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);
- }
- } else if (ch == '\'') {
- styler.ColourTo(i, state);
- state = SCE_P_DEFAULT;
- }
- } else if (state == SCE_P_TRIPLE) {
- if (ch == '\'' && chPrev == '\'' && chPrev2 == '\'') {
- styler.ColourTo(i, state);
- state = SCE_P_DEFAULT;
- }
- } else if (state == SCE_P_TRIPLEDOUBLE) {
- // =end terminates the comment block
- if (ch == 'd' && chPrev == 'n' && chPrev2 == 'e') {
- if (styler.SafeGetCharAt(i - 3) == '=') {
- styler.ColourTo(i, state);
- state = SCE_P_DEFAULT;
- }
- }
+ 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 == '#' ) {
+ //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);
+ chNext2 = styler.SafeGetCharAt(i + 2);
+ }
+ // 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++;
+ }
+ }
+
+ 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);
+ }
+}
+
+// Helper functions for folding
+
+static void getPrevWord(int pos,
+ char *prevWord,
+ Accessor &styler,
+ int word_state)
+{
+ int i;
+ styler.Flush();
+ for (i = pos - 1; i > 0; i--) {
+ if (actual_style(styler.StyleAt(i)) != word_state) {
+ i++;
+ break;
+ }
+ }
+ if (i < pos - MAX_KEYWORD_LENGTH) // overflow
+ i = pos - MAX_KEYWORD_LENGTH;
+ char *dst = prevWord;
+ for (; i <= pos; i++) {
+ *dst++ = styler[i];
+ }
+ *dst = 0;
+}
+
+static bool keywordIsAmbiguous(const char *prevWord)
+{
+ // Order from most likely used to least likely
+ // Lots of ways to do a loop in Ruby besides 'while/until'
+ if (!strcmp(prevWord, "if")
+ || !strcmp(prevWord, "do")
+ || !strcmp(prevWord, "while")
+ || !strcmp(prevWord, "unless")
+ || !strcmp(prevWord, "until")) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+static bool inline iswhitespace(char ch) {
+ return ch == ' ' || ch == '\t';
+}
+
+// Demote keywords in the following conditions:
+// if, while, unless, until modify a statement
+// do after a while or until, as a noise word (like then after if)
+
+static bool keywordIsModifier(const char *word,
+ int pos,
+ Accessor &styler)
+{
+ if (word[0] == 'd' && word[1] == 'o' && !word[2]) {
+ return keywordDoStartsLoop(pos, styler);
+ }
+ char ch;
+ int style = SCE_RB_DEFAULT;
+ int lineStart = styler.GetLine(pos);
+ int lineStartPosn = styler.LineStart(lineStart);
+ styler.Flush();
+ while (--pos >= lineStartPosn) {
+ style = actual_style(styler.StyleAt(pos));
+ if (style == SCE_RB_DEFAULT) {
+ if (iswhitespace(ch = styler[pos])) {
+ //continue
+ } else if (ch == '\r' || ch == '\n') {
+ // Scintilla's LineStart() and GetLine() routines aren't
+ // platform-independent, so if we have text prepared with
+ // a different system we can't rely on it.
+ return false;
}
+ } else {
+ break;
}
- chPrev2 = chPrev;
- chPrev = ch;
- }
- if (state == SCE_P_WORD) {
- ClassifyWordRb(styler.GetStartSegment(), lengthDoc-1, keywords, styler, prevWord);
- } else {
- styler.ColourTo(lengthDoc-1, state);
- }
+ }
+ if (pos < lineStartPosn) {
+ return false; //XXX not quite right if the prev line is a continuation
+ }
+ // First things where the action is unambiguous
+ switch (style) {
+ case SCE_RB_DEFAULT:
+ case SCE_RB_COMMENTLINE:
+ case SCE_RB_POD:
+ case SCE_RB_CLASSNAME:
+ case SCE_RB_DEFNAME:
+ case SCE_RB_MODULE_NAME:
+ return false;
+ case SCE_RB_OPERATOR:
+ break;
+ case SCE_RB_WORD:
+ // Watch out for uses of 'else if'
+ //XXX: Make a list of other keywords where 'if' isn't a modifier
+ // and can appear legitimately
+ // Formulate this to avoid warnings from most compilers
+ if (strcmp(word, "if") == 0) {
+ char prevWord[MAX_KEYWORD_LENGTH + 1];
+ getPrevWord(pos, prevWord, styler, SCE_RB_WORD);
+ return strcmp(prevWord, "else") != 0;
+ }
+ return true;
+ default:
+ return true;
+ }
+ // Assume that if the keyword follows an operator,
+ // usually it's a block assignment, like
+ // a << if x then y else z
+
+ ch = styler[pos];
+ switch (ch) {
+ case ')':
+ case ']':
+ case '}':
+ return true;
+ default:
+ return false;
+ }
}
-static void FoldRbDoc(unsigned int startPos, int length, int initStyle,
- WordList *[], Accessor &styler) {
- int lengthDoc = startPos + length;
+#define WHILE_BACKWARDS "elihw"
+#define UNTIL_BACKWARDS "litnu"
- // Backtrack to previous line in case need to fix its fold status
+// Nothing fancy -- look to see if we follow a while/until somewhere
+// on the current line
+
+static bool keywordDoStartsLoop(int pos,
+ Accessor &styler)
+{
+ char ch;
+ int style;
+ int lineStart = styler.GetLine(pos);
+ int lineStartPosn = styler.LineStart(lineStart);
+ styler.Flush();
+ while (--pos >= lineStartPosn) {
+ style = actual_style(styler.StyleAt(pos));
+ if (style == SCE_RB_DEFAULT) {
+ if ((ch = styler[pos]) == '\r' || ch == '\n') {
+ // Scintilla's LineStart() and GetLine() routines aren't
+ // platform-independent, so if we have text prepared with
+ // a different system we can't rely on it.
+ return false;
+ }
+ } else if (style == SCE_RB_WORD) {
+ // Check for while or until, but write the word in backwards
+ char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
+ char *dst = prevWord;
+ int wordLen = 0;
+ int start_word;
+ for (start_word = pos;
+ start_word >= lineStartPosn && actual_style(styler.StyleAt(start_word)) == SCE_RB_WORD;
+ start_word--) {
+ if (++wordLen < MAX_KEYWORD_LENGTH) {
+ *dst++ = styler[start_word];
+ }
+ }
+ *dst = 0;
+ // Did we see our keyword?
+ if (!strcmp(prevWord, WHILE_BACKWARDS)
+ || !strcmp(prevWord, UNTIL_BACKWARDS)) {
+ return true;
+ }
+ // We can move pos to the beginning of the keyword, and then
+ // accept another decrement, as we can never have two contiguous
+ // keywords:
+ // word1 word2
+ // ^
+ // <- move to start_word
+ // ^
+ // <- loop decrement
+ // ^ # pointing to end of word1 is fine
+ pos = start_word;
+ }
+ }
+ return false;
+}
+
+/*
+ * Folding Ruby
+ *
+ * The language is quite complex to analyze without a full parse.
+ * For example, this line shouldn't affect fold level:
+ *
+ * print "hello" if feeling_friendly?
+ *
+ * Neither should this:
+ *
+ * print "hello" \
+ * if feeling_friendly?
+ *
+ *
+ * But this should:
+ *
+ * if feeling_friendly? #++
+ * print "hello" \
+ * print "goodbye"
+ * end #--
+ *
+ * So we cheat, by actually looking at the existing indentation
+ * levels for each line, and just echoing it back. Like Python.
+ * Then if we get better at it, we'll take braces into consideration,
+ * which always affect folding levels.
+
+ * How the keywords should work:
+ * No effect:
+ * __FILE__ __LINE__ BEGIN END alias and
+ * defined? false in nil not or self super then
+ * true undef
+
+ * Always increment:
+ * begin class def do for module when {
+ *
+ * Always decrement:
+ * end }
+ *
+ * Increment if these start a statement
+ * if unless until while -- do nothing if they're modifiers
+
+ * These end a block if there's no modifier, but don't bother
+ * break next redo retry return yield
+ *
+ * These temporarily de-indent, but re-indent
+ * case else elsif ensure rescue
+ *
+ * This means that the folder reflects indentation rather
+ * than setting it. The language-service updates indentation
+ * when users type return and finishes entering de-denters.
+ *
+ * Later offer to fold POD, here-docs, strings, and blocks of comments
+ */
+
+static void FoldRbDoc(unsigned int startPos, int length, int initStyle,
+ WordList *[], Accessor &styler) {
+ const bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
+ bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
+
+ synchronizeDocStart(startPos, length, initStyle, styler, // ref args
+ false);
+ unsigned int endPos = startPos + length;
+ int visibleChars = 0;
int lineCurrent = styler.GetLine(startPos);
- if (startPos > 0) {
- if (lineCurrent > 0) {
- lineCurrent--;
- startPos = styler.LineStart(lineCurrent);
- if (startPos == 0)
- initStyle = SCE_P_DEFAULT;
- else
- initStyle = styler.StyleAt(startPos-1);
- }
- }
- int state = initStyle & 31;
- int spaceFlags = 0;
- int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, IsRbComment);
- if ((state == SCE_P_TRIPLE) || (state == SCE_P_TRIPLEDOUBLE))
- indentCurrent |= SC_FOLDLEVELWHITEFLAG;
+ int levelPrev = startPos == 0 ? 0 : (styler.LevelAt(lineCurrent)
+ & SC_FOLDLEVELNUMBERMASK
+ & ~SC_FOLDLEVELBASE);
+ int levelCurrent = levelPrev;
char chNext = styler[startPos];
- for (int i = startPos; i < lengthDoc; i++) {
+ int styleNext = styler.StyleAt(startPos);
+ int stylePrev = startPos <= 1 ? SCE_RB_DEFAULT : styler.StyleAt(startPos - 1);
+ bool buffer_ends_with_eol = false;
+ for (unsigned int i = startPos; i < endPos; i++) {
char ch = chNext;
chNext = styler.SafeGetCharAt(i + 1);
- int style = styler.StyleAt(i) & 31;
-
- if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == lengthDoc)) {
- int lev = indentCurrent;
- int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags, IsRbComment);
- if ((style == SCE_P_TRIPLE) || (style== SCE_P_TRIPLEDOUBLE))
- indentNext |= SC_FOLDLEVELWHITEFLAG;
- if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) {
- // Only non whitespace lines can be headers
- if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) {
- lev |= SC_FOLDLEVELHEADERFLAG;
- } else if (indentNext & SC_FOLDLEVELWHITEFLAG) {
- // Line after is blank so check the next - maybe should continue further?
- int spaceFlags2 = 0;
- int indentNext2 = styler.IndentAmount(lineCurrent + 2, &spaceFlags2, IsRbComment);
- if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext2 & SC_FOLDLEVELNUMBERMASK)) {
- lev |= SC_FOLDLEVELHEADERFLAG;
- }
+ int style = styleNext;
+ styleNext = styler.StyleAt(i + 1);
+ bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
+ if (style == SCE_RB_COMMENTLINE) {
+ if (foldComment && stylePrev != SCE_RB_COMMENTLINE) {
+ if (chNext == '{') {
+ levelCurrent++;
+ } else if (chNext == '}') {
+ levelCurrent--;
}
+ }
+ } else if (style == SCE_RB_OPERATOR) {
+ if (strchr("[{(", ch)) {
+ levelCurrent++;
+ } else if (strchr(")}]", ch)) {
+ // Don't decrement below 0
+ if (levelCurrent > 0)
+ levelCurrent--;
}
- indentCurrent = indentNext;
- styler.SetLevel(lineCurrent, lev);
+ } else if (style == SCE_RB_WORD && styleNext != SCE_RB_WORD) {
+ // Look at the keyword on the left and decide what to do
+ char prevWord[MAX_KEYWORD_LENGTH + 1]; // 1 byte for zero
+ prevWord[0] = 0;
+ getPrevWord(i, prevWord, styler, SCE_RB_WORD);
+ if (!strcmp(prevWord, "end")) {
+ // Don't decrement below 0
+ if (levelCurrent > 0)
+ levelCurrent--;
+ } else if ( !strcmp(prevWord, "if")
+ || !strcmp(prevWord, "def")
+ || !strcmp(prevWord, "class")
+ || !strcmp(prevWord, "module")
+ || !strcmp(prevWord, "begin")
+ || !strcmp(prevWord, "case")
+ || !strcmp(prevWord, "do")
+ || !strcmp(prevWord, "while")
+ || !strcmp(prevWord, "unless")
+ || !strcmp(prevWord, "until")
+ || !strcmp(prevWord, "for")
+ ) {
+ levelCurrent++;
+ }
+ }
+ if (atEOL) {
+ int lev = levelPrev;
+ if (visibleChars == 0 && foldCompact)
+ lev |= SC_FOLDLEVELWHITEFLAG;
+ if ((levelCurrent > levelPrev) && (visibleChars > 0))
+ lev |= SC_FOLDLEVELHEADERFLAG;
+ styler.SetLevel(lineCurrent, lev|SC_FOLDLEVELBASE);
lineCurrent++;
- }
- }
+ levelPrev = levelCurrent;
+ visibleChars = 0;
+ buffer_ends_with_eol = true;
+ } else if (!isspacechar(ch)) {
+ visibleChars++;
+ buffer_ends_with_eol = false;
+ }
+ }
+ // Fill in the real level of the next line, keeping the current flags as they will be filled in later
+ if (!buffer_ends_with_eol) {
+ lineCurrent++;
+ int new_lev = levelCurrent;
+ if (visibleChars == 0 && foldCompact)
+ new_lev |= SC_FOLDLEVELWHITEFLAG;
+ if ((levelCurrent > levelPrev) && (visibleChars > 0))
+ new_lev |= SC_FOLDLEVELHEADERFLAG;
+ levelCurrent = new_lev;
+ }
+ styler.SetLevel(lineCurrent, levelCurrent|SC_FOLDLEVELBASE);
}
static const char * const rubyWordListDesc[] = {