]>
Commit | Line | Data |
---|---|---|
65ec6247 RD |
1 | // Scintilla source code edit control |
2 | /** @file LexRuby.cxx | |
3 | ** Lexer for Ruby. | |
4 | **/ | |
5 | // Copyright 2001- by Clemens Wyss <wys@helbling.ch> | |
6 | // The License.txt file describes the conditions under which this software may be distributed. | |
7 | ||
8 | #include <stdlib.h> | |
9 | #include <string.h> | |
10 | #include <ctype.h> | |
11 | #include <stdio.h> | |
12 | #include <stdarg.h> | |
13 | ||
14 | #include "Platform.h" | |
15 | ||
16 | #include "PropSet.h" | |
17 | #include "Accessor.h" | |
18 | #include "KeyWords.h" | |
19 | #include "Scintilla.h" | |
20 | #include "SciLexer.h" | |
21 | ||
22 | static void ClassifyWordRb(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler, char *prevWord) { | |
23 | char s[100]; | |
1a2fb4cd | 24 | bool wordIsNumber = isdigit(styler[start]) != 0; |
65ec6247 RD |
25 | for (unsigned int i = 0; i < end - start + 1 && i < 30; i++) { |
26 | s[i] = styler[start + i]; | |
27 | s[i + 1] = '\0'; | |
28 | } | |
29 | char chAttr = SCE_P_IDENTIFIER; | |
30 | if (0 == strcmp(prevWord, "class")) | |
31 | chAttr = SCE_P_CLASSNAME; | |
32 | else if (0 == strcmp(prevWord, "module")) | |
33 | chAttr = SCE_P_CLASSNAME; | |
34 | else if (0 == strcmp(prevWord, "def")) | |
35 | chAttr = SCE_P_DEFNAME; | |
36 | else if (wordIsNumber) | |
37 | chAttr = SCE_P_NUMBER; | |
38 | else if (keywords.InList(s)) | |
39 | chAttr = SCE_P_WORD; | |
40 | // make sure that dot-qualifiers inside the word are lexed correct | |
41 | else for (unsigned int i = 0; i < end - start + 1; i++) { | |
42 | if (styler[start + i] == '.') { | |
43 | styler.ColourTo(start + i - 1, chAttr); | |
44 | styler.ColourTo(start + i, SCE_P_OPERATOR); | |
45 | } | |
46 | } | |
47 | styler.ColourTo(end, chAttr); | |
48 | strcpy(prevWord, s); | |
49 | } | |
50 | ||
51 | static bool IsRbComment(Accessor &styler, int pos, int len) { | |
52 | return len>0 && styler[pos]=='#'; | |
53 | } | |
54 | ||
55 | static bool IsRbStringStart(char ch, char chNext, char chNext2) { | |
56 | if (ch == '\'' || ch == '"') | |
57 | return true; | |
58 | if (ch == 'u' || ch == 'U') { | |
59 | if (chNext == '"' || chNext == '\'') | |
60 | return true; | |
61 | if ((chNext == 'r' || chNext == 'R') && (chNext2 == '"' || chNext2 == '\'')) | |
62 | return true; | |
63 | } | |
64 | if ((ch == 'r' || ch == 'R') && (chNext == '"' || chNext == '\'')) | |
65 | return true; | |
66 | ||
67 | return false; | |
68 | } | |
69 | ||
70 | static bool IsRbWordStart(char ch, char chNext, char chNext2) { | |
71 | return (iswordchar(ch) && !IsRbStringStart(ch, chNext, chNext2)); | |
72 | } | |
73 | ||
74 | /* Return the state to use for the string starting at i; *nextIndex will be set to the first index following the quote(s) */ | |
75 | static int GetRbStringState(Accessor &styler, int i, int *nextIndex) { | |
76 | char ch = styler.SafeGetCharAt(i); | |
77 | char chNext = styler.SafeGetCharAt(i + 1); | |
78 | ||
79 | // Advance beyond r, u, or ur prefix, but bail if there are any unexpected chars | |
80 | if (ch == 'r' || ch == 'R') { | |
81 | i++; | |
82 | ch = styler.SafeGetCharAt(i); | |
83 | chNext = styler.SafeGetCharAt(i + 1); | |
84 | } | |
85 | else if (ch == 'u' || ch == 'U') { | |
86 | if (chNext == 'r' || chNext == 'R') | |
87 | i += 2; | |
88 | else | |
89 | i += 1; | |
90 | ch = styler.SafeGetCharAt(i); | |
91 | chNext = styler.SafeGetCharAt(i + 1); | |
92 | } | |
93 | ||
94 | if (ch != '"' && ch != '\'') { | |
95 | *nextIndex = i + 1; | |
96 | return SCE_P_DEFAULT; | |
97 | } | |
98 | ||
99 | if (ch == chNext && ch == styler.SafeGetCharAt(i + 2)) { | |
100 | *nextIndex = i + 3; | |
101 | ||
102 | if (ch == '"') | |
103 | return SCE_P_TRIPLEDOUBLE; | |
104 | else | |
105 | return SCE_P_TRIPLE; | |
106 | } else { | |
107 | *nextIndex = i + 1; | |
108 | ||
109 | if (ch == '"') | |
110 | return SCE_P_STRING; | |
111 | else | |
112 | return SCE_P_CHARACTER; | |
113 | } | |
114 | } | |
115 | ||
116 | static void ColouriseRbDoc(unsigned int startPos, int length, int initStyle, | |
117 | WordList *keywordlists[], Accessor &styler) { | |
118 | ||
119 | int lengthDoc = startPos + length; | |
120 | ||
121 | // Backtrack to previous line in case need to fix its tab whinging | |
122 | if (startPos > 0) { | |
123 | int lineCurrent = styler.GetLine(startPos); | |
124 | if (lineCurrent > 0) { | |
125 | startPos = styler.LineStart(lineCurrent-1); | |
126 | if (startPos == 0) | |
127 | initStyle = SCE_P_DEFAULT; | |
128 | else | |
129 | initStyle = styler.StyleAt(startPos-1); | |
130 | } | |
131 | } | |
132 | ||
133 | // Ruby uses a different mask because bad indentation is marked by oring with 32 | |
134 | styler.StartAt(startPos, 127); | |
135 | ||
136 | WordList &keywords = *keywordlists[0]; | |
137 | ||
138 | int whingeLevel = styler.GetPropertyInt("tab.timmy.whinge.level"); | |
139 | char prevWord[200]; | |
140 | prevWord[0] = '\0'; | |
141 | if (length == 0) | |
142 | return ; | |
143 | ||
144 | int state = initStyle & 31; | |
145 | ||
146 | int nextIndex = 0; | |
147 | char chPrev = ' '; | |
148 | char chPrev2 = ' '; | |
149 | char chNext = styler[startPos]; | |
150 | styler.StartSegment(startPos); | |
151 | bool atStartLine = true; | |
152 | int spaceFlags = 0; | |
153 | for (int i = startPos; i < lengthDoc; i++) { | |
154 | ||
155 | if (atStartLine) { | |
156 | char chBad = static_cast<char>(64); | |
157 | char chGood = static_cast<char>(0); | |
158 | char chFlags = chGood; | |
159 | if (whingeLevel == 1) { | |
160 | chFlags = (spaceFlags & wsInconsistent) ? chBad : chGood; | |
161 | } else if (whingeLevel == 2) { | |
162 | chFlags = (spaceFlags & wsSpaceTab) ? chBad : chGood; | |
163 | } else if (whingeLevel == 3) { | |
164 | chFlags = (spaceFlags & wsSpace) ? chBad : chGood; | |
165 | } else if (whingeLevel == 4) { | |
166 | chFlags = (spaceFlags & wsTab) ? chBad : chGood; | |
167 | } | |
168 | styler.SetFlags(chFlags, static_cast<char>(state)); | |
169 | atStartLine = false; | |
170 | } | |
171 | ||
172 | char ch = chNext; | |
173 | chNext = styler.SafeGetCharAt(i + 1); | |
174 | char chNext2 = styler.SafeGetCharAt(i + 2); | |
175 | ||
176 | if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == lengthDoc)) { | |
177 | if ((state == SCE_P_DEFAULT) || (state == SCE_P_TRIPLE) || (state == SCE_P_TRIPLEDOUBLE)) { | |
178 | // Perform colourisation of white space and triple quoted strings at end of each line to allow | |
179 | // tab marking to work inside white space and triple quoted strings | |
180 | styler.ColourTo(i, state); | |
181 | } | |
182 | atStartLine = true; | |
183 | } | |
184 | ||
185 | if (styler.IsLeadByte(ch)) { | |
186 | chNext = styler.SafeGetCharAt(i + 2); | |
187 | chPrev = ' '; | |
188 | chPrev2 = ' '; | |
189 | i += 1; | |
190 | continue; | |
191 | } | |
192 | ||
193 | if (state == SCE_P_STRINGEOL) { | |
194 | if (ch != '\r' && ch != '\n') { | |
195 | styler.ColourTo(i - 1, state); | |
196 | state = SCE_P_DEFAULT; | |
197 | } | |
198 | } | |
199 | if (state == SCE_P_DEFAULT) { | |
200 | if (IsRbWordStart(ch, chNext, chNext2)) { | |
201 | styler.ColourTo(i - 1, state); | |
202 | state = SCE_P_WORD; | |
203 | } else if (ch == '#') { | |
204 | styler.ColourTo(i - 1, state); | |
205 | state = chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE; | |
206 | } else if (ch == '=' && chNext == 'b') { | |
207 | // =begin indicates the start of a comment (doc) block | |
208 | if(styler.SafeGetCharAt(i + 2) == 'e' && styler.SafeGetCharAt(i + 3) == 'g' && styler.SafeGetCharAt(i + 4) == 'i' && styler.SafeGetCharAt(i + 5) == 'n') { | |
209 | styler.ColourTo(i - 1, state); | |
210 | state = SCE_P_TRIPLEDOUBLE; //SCE_C_COMMENT; | |
211 | } | |
212 | } else if (IsRbStringStart(ch, chNext, chNext2)) { | |
213 | styler.ColourTo(i - 1, state); | |
214 | state = GetRbStringState(styler, i, &nextIndex); | |
215 | if (nextIndex != i + 1) { | |
216 | i = nextIndex - 1; | |
217 | ch = ' '; | |
218 | chPrev = ' '; | |
219 | chNext = styler.SafeGetCharAt(i + 1); | |
220 | } | |
221 | } else if (isoperator(ch)) { | |
222 | styler.ColourTo(i - 1, state); | |
223 | styler.ColourTo(i, SCE_P_OPERATOR); | |
9e730a78 | 224 | } |
65ec6247 RD |
225 | } else if (state == SCE_P_WORD) { |
226 | if (!iswordchar(ch)) { | |
227 | ClassifyWordRb(styler.GetStartSegment(), i - 1, keywords, styler, prevWord); | |
228 | state = SCE_P_DEFAULT; | |
229 | if (ch == '#') { | |
230 | state = chNext == '#' ? SCE_P_COMMENTBLOCK : SCE_P_COMMENTLINE; | |
231 | } else if (IsRbStringStart(ch, chNext, chNext2)) { | |
232 | styler.ColourTo(i - 1, state); | |
233 | state = GetRbStringState(styler, i, &nextIndex); | |
234 | if (nextIndex != i + 1) { | |
235 | i = nextIndex - 1; | |
236 | ch = ' '; | |
237 | chPrev = ' '; | |
238 | chNext = styler.SafeGetCharAt(i + 1); | |
239 | } | |
240 | } else if (isoperator(ch)) { | |
241 | styler.ColourTo(i, SCE_P_OPERATOR); | |
242 | } | |
243 | } | |
244 | } else { | |
245 | if (state == SCE_P_COMMENTLINE || state == SCE_P_COMMENTBLOCK) { | |
246 | if (ch == '\r' || ch == '\n') { | |
247 | styler.ColourTo(i - 1, state); | |
248 | state = SCE_P_DEFAULT; | |
249 | } | |
250 | } else if (state == SCE_P_STRING) { | |
251 | if ((ch == '\r' || ch == '\n') && (chPrev != '\\')) { | |
252 | styler.ColourTo(i - 1, state); | |
253 | state = SCE_P_STRINGEOL; | |
254 | } else if (ch == '\\') { | |
255 | if (chNext == '\"' || chNext == '\'' || chNext == '\\') { | |
256 | i++; | |
257 | ch = chNext; | |
258 | chNext = styler.SafeGetCharAt(i + 1); | |
259 | } | |
260 | } else if (ch == '\"') { | |
261 | styler.ColourTo(i, state); | |
262 | state = SCE_P_DEFAULT; | |
263 | } | |
264 | } else if (state == SCE_P_CHARACTER) { | |
265 | if ((ch == '\r' || ch == '\n') && (chPrev != '\\')) { | |
266 | styler.ColourTo(i - 1, state); | |
267 | state = SCE_P_STRINGEOL; | |
268 | } else if (ch == '\\') { | |
269 | if (chNext == '\"' || chNext == '\'' || chNext == '\\') { | |
270 | i++; | |
271 | ch = chNext; | |
272 | chNext = styler.SafeGetCharAt(i + 1); | |
273 | } | |
274 | } else if (ch == '\'') { | |
275 | styler.ColourTo(i, state); | |
276 | state = SCE_P_DEFAULT; | |
277 | } | |
278 | } else if (state == SCE_P_TRIPLE) { | |
279 | if (ch == '\'' && chPrev == '\'' && chPrev2 == '\'') { | |
280 | styler.ColourTo(i, state); | |
281 | state = SCE_P_DEFAULT; | |
282 | } | |
283 | } else if (state == SCE_P_TRIPLEDOUBLE) { | |
284 | // =end terminates the comment block | |
285 | if (ch == 'd' && chPrev == 'n' && chPrev2 == 'e') { | |
286 | if (styler.SafeGetCharAt(i - 3) == '=') { | |
287 | styler.ColourTo(i, state); | |
288 | state = SCE_P_DEFAULT; | |
289 | } | |
290 | } | |
291 | } | |
292 | } | |
293 | chPrev2 = chPrev; | |
294 | chPrev = ch; | |
295 | } | |
296 | if (state == SCE_P_WORD) { | |
1a2fb4cd | 297 | ClassifyWordRb(styler.GetStartSegment(), lengthDoc-1, keywords, styler, prevWord); |
65ec6247 | 298 | } else { |
1a2fb4cd | 299 | styler.ColourTo(lengthDoc-1, state); |
65ec6247 RD |
300 | } |
301 | } | |
302 | ||
303 | static void FoldRbDoc(unsigned int startPos, int length, int initStyle, | |
304 | WordList *[], Accessor &styler) { | |
305 | int lengthDoc = startPos + length; | |
306 | ||
307 | // Backtrack to previous line in case need to fix its fold status | |
308 | int lineCurrent = styler.GetLine(startPos); | |
309 | if (startPos > 0) { | |
310 | if (lineCurrent > 0) { | |
311 | lineCurrent--; | |
312 | startPos = styler.LineStart(lineCurrent); | |
313 | if (startPos == 0) | |
314 | initStyle = SCE_P_DEFAULT; | |
315 | else | |
316 | initStyle = styler.StyleAt(startPos-1); | |
317 | } | |
318 | } | |
319 | int state = initStyle & 31; | |
320 | int spaceFlags = 0; | |
321 | int indentCurrent = styler.IndentAmount(lineCurrent, &spaceFlags, IsRbComment); | |
322 | if ((state == SCE_P_TRIPLE) || (state == SCE_P_TRIPLEDOUBLE)) | |
323 | indentCurrent |= SC_FOLDLEVELWHITEFLAG; | |
324 | char chNext = styler[startPos]; | |
325 | for (int i = startPos; i < lengthDoc; i++) { | |
326 | char ch = chNext; | |
327 | chNext = styler.SafeGetCharAt(i + 1); | |
328 | int style = styler.StyleAt(i) & 31; | |
329 | ||
330 | if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == lengthDoc)) { | |
331 | int lev = indentCurrent; | |
332 | int indentNext = styler.IndentAmount(lineCurrent + 1, &spaceFlags, IsRbComment); | |
333 | if ((style == SCE_P_TRIPLE) || (style== SCE_P_TRIPLEDOUBLE)) | |
334 | indentNext |= SC_FOLDLEVELWHITEFLAG; | |
335 | if (!(indentCurrent & SC_FOLDLEVELWHITEFLAG)) { | |
336 | // Only non whitespace lines can be headers | |
337 | if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext & SC_FOLDLEVELNUMBERMASK)) { | |
338 | lev |= SC_FOLDLEVELHEADERFLAG; | |
339 | } else if (indentNext & SC_FOLDLEVELWHITEFLAG) { | |
340 | // Line after is blank so check the next - maybe should continue further? | |
341 | int spaceFlags2 = 0; | |
342 | int indentNext2 = styler.IndentAmount(lineCurrent + 2, &spaceFlags2, IsRbComment); | |
343 | if ((indentCurrent & SC_FOLDLEVELNUMBERMASK) < (indentNext2 & SC_FOLDLEVELNUMBERMASK)) { | |
344 | lev |= SC_FOLDLEVELHEADERFLAG; | |
345 | } | |
346 | } | |
347 | } | |
348 | indentCurrent = indentNext; | |
349 | styler.SetLevel(lineCurrent, lev); | |
350 | lineCurrent++; | |
351 | } | |
352 | } | |
353 | } | |
9e730a78 RD |
354 | |
355 | static const char * const rubyWordListDesc[] = { | |
356 | "Keywords", | |
357 | 0 | |
358 | }; | |
359 | ||
360 | LexerModule lmRuby(SCLEX_RUBY, ColouriseRbDoc, "ruby", FoldRbDoc, rubyWordListDesc); |