1 | // Scintilla source code edit control |
2 | /** @file LexProgress.cxx |
3 | ** Lexer for Progress 4GL. |
4 | ** Based on LexCPP.cxx of Neil Hodgson <neilh@scintilla.org> |
5 | **/ |
6 | // Copyright 2006-2016 by Yuval Papish <Yuval@YuvCom.com> |
7 | // The License.txt file describes the conditions under which this software may be distributed. |
8 | |
9 | /** TODO: |
10 | |
11 | SpeedScript support in html lexer |
12 | Differentiate between labels and variables |
13 | Option 1: By symbols table |
14 | Option 2: As a single unidentified symbol in a sytactical line |
15 | |
16 | **/ |
17 | |
18 | #include <stdlib.h> |
19 | #include <string.h> |
20 | #include <stdio.h> |
21 | #include <stdarg.h> |
22 | #include <assert.h> |
23 | #include <ctype.h> |
24 | |
25 | #include <string> |
26 | #include <string_view> |
27 | #include <vector> |
28 | #include <map> |
29 | #include <algorithm> |
30 | #include <functional> |
31 | |
32 | #include "ILexer.h" |
33 | #include "Scintilla.h" |
34 | #include "SciLexer.h" |
35 | |
36 | #include "WordList.h" |
37 | #include "LexAccessor.h" |
38 | #include "StyleContext.h" |
39 | #include "CharacterSet.h" |
40 | #include "LexerModule.h" |
41 | #include "OptionSet.h" |
42 | #include "SparseState.h" |
43 | #include "DefaultLexer.h" |
44 | |
45 | using namespace Scintilla; |
46 | using namespace Lexilla; |
47 | |
48 | namespace { |
49 | // Use an unnamed namespace to protect the functions and classes from name conflicts |
50 | |
51 | bool IsSpaceEquiv(int state) { |
52 | return (state == SCE_ABL_COMMENT || |
53 | state == SCE_ABL_LINECOMMENT || |
54 | state == SCE_ABL_DEFAULT); |
55 | } |
56 | |
57 | void highlightTaskMarker(StyleContext &sc, LexAccessor &styler, WordList &markerList){ |
58 | if ((isoperator(sc.chPrev) || IsASpace(sc.chPrev)) && markerList.Length()) { |
59 | const int lengthMarker = 50; |
60 | char marker[lengthMarker+1]; |
61 | Sci_Position currPos = (Sci_Position) sc.currentPos; |
62 | Sci_Position i = 0; |
63 | while (i < lengthMarker) { |
64 | char ch = styler.SafeGetCharAt(currPos + i); |
65 | if (IsASpace(ch) || isoperator(ch)) { |
66 | break; |
67 | } |
68 | marker[i] = ch; |
69 | i++; |
70 | } |
71 | marker[i] = '\0'; |
72 | if (markerList.InListAbbreviated (marker,'(')) { |
73 | sc.SetState(SCE_ABL_TASKMARKER); |
74 | } |
75 | } |
76 | } |
77 | |
78 | bool (int style) { |
79 | return style == SCE_ABL_COMMENT; |
80 | // style == SCE_ABL_LINECOMMENT; Only block comments are used for folding |
81 | } |
82 | |
83 | // Options used for LexerABL |
84 | struct OptionsABL { |
85 | bool fold; |
86 | bool foldSyntaxBased; |
87 | bool ; |
88 | bool ; |
89 | bool foldCompact; |
90 | OptionsABL() { |
91 | fold = false; |
92 | foldSyntaxBased = true; |
93 | foldComment = true; |
94 | foldCommentMultiline = true; |
95 | foldCompact = false; |
96 | } |
97 | }; |
98 | |
99 | const char *const ablWordLists[] = { |
100 | "Primary keywords and identifiers" , |
101 | "Keywords that opens a block, only when used to begin a syntactic line" , |
102 | "Keywords that opens a block anywhere in a syntactic line" , |
103 | "Task Marker" , /* "END MODIFY START TODO" */ |
104 | 0, |
105 | }; |
106 | |
107 | struct OptionSetABL : public OptionSet<OptionsABL> { |
108 | OptionSetABL() { |
109 | DefineProperty("fold" , &OptionsABL::fold); |
110 | |
111 | DefineProperty("fold.abl.syntax.based" , &OptionsABL::foldSyntaxBased, |
112 | "Set this property to 0 to disable syntax based folding." ); |
113 | |
114 | DefineProperty("fold.comment" , &OptionsABL::foldComment, |
115 | "This option enables folding multi-line comments and explicit fold points when using the ABL lexer. " ); |
116 | |
117 | DefineProperty("fold.abl.comment.multiline" , &OptionsABL::foldCommentMultiline, |
118 | "Set this property to 0 to disable folding multi-line comments when fold.comment=1." ); |
119 | |
120 | DefineProperty("fold.compact" , &OptionsABL::foldCompact); |
121 | |
122 | DefineWordListSets(ablWordLists); |
123 | } |
124 | }; |
125 | } |
126 | |
127 | class LexerABL : public DefaultLexer { |
128 | CharacterSet setWord; |
129 | CharacterSet setNegationOp; |
130 | CharacterSet setArithmethicOp; |
131 | CharacterSet setRelOp; |
132 | CharacterSet setLogicalOp; |
133 | CharacterSet setWordStart; |
134 | WordList keywords1; // regular keywords |
135 | WordList keywords2; // block opening keywords, only when isSentenceStart |
136 | WordList keywords3; // block opening keywords |
137 | WordList keywords4; // Task Marker |
138 | OptionsABL options; |
139 | OptionSetABL osABL; |
140 | public: |
141 | LexerABL() : |
142 | DefaultLexer("abl" , SCLEX_PROGRESS), |
143 | setWord(CharacterSet::setAlphaNum, "_" , 0x80, true), |
144 | setNegationOp(CharacterSet::setNone, "!" ), |
145 | setArithmethicOp(CharacterSet::setNone, "+-/*%" ), |
146 | setRelOp(CharacterSet::setNone, "=!<>" ), |
147 | setLogicalOp(CharacterSet::setNone, "|&" ){ |
148 | } |
149 | virtual ~LexerABL() { |
150 | } |
151 | void SCI_METHOD Release() override { |
152 | delete this; |
153 | } |
154 | int SCI_METHOD Version() const override { |
155 | return lvRelease5; |
156 | } |
157 | const char * SCI_METHOD PropertyNames() override { |
158 | return osABL.PropertyNames(); |
159 | } |
160 | int SCI_METHOD PropertyType(const char *name) override { |
161 | return osABL.PropertyType(name); |
162 | } |
163 | const char * SCI_METHOD DescribeProperty(const char *name) override { |
164 | return osABL.DescribeProperty(name); |
165 | } |
166 | Sci_Position SCI_METHOD PropertySet(const char *key, const char *val) override ; |
167 | const char * SCI_METHOD PropertyGet(const char *key) override { |
168 | return osABL.PropertyGet(key); |
169 | } |
170 | |
171 | const char * SCI_METHOD DescribeWordListSets() override { |
172 | return osABL.DescribeWordListSets(); |
173 | } |
174 | Sci_Position SCI_METHOD WordListSet(int n, const char *wl) override; |
175 | void SCI_METHOD Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; |
176 | void SCI_METHOD Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) override; |
177 | |
178 | void * SCI_METHOD PrivateCall(int, void *) override { |
179 | return 0; |
180 | } |
181 | int SCI_METHOD LineEndTypesSupported() override { |
182 | return SC_LINE_END_TYPE_DEFAULT; |
183 | } |
184 | static ILexer5 *LexerFactoryABL() { |
185 | return new LexerABL(); |
186 | } |
187 | }; |
188 | |
189 | Sci_Position SCI_METHOD LexerABL::PropertySet(const char *key, const char *val) { |
190 | if (osABL.PropertySet(&options, key, val)) { |
191 | return 0; |
192 | } |
193 | return -1; |
194 | } |
195 | |
196 | Sci_Position SCI_METHOD LexerABL::WordListSet(int n, const char *wl) { |
197 | WordList *wordListN = 0; |
198 | switch (n) { |
199 | case 0: |
200 | wordListN = &keywords1; |
201 | break; |
202 | case 1: |
203 | wordListN = &keywords2; |
204 | break; |
205 | case 2: |
206 | wordListN = &keywords3; |
207 | break; |
208 | case 3: |
209 | wordListN = &keywords4; |
210 | break; |
211 | } |
212 | Sci_Position firstModification = -1; |
213 | if (wordListN) { |
214 | WordList wlNew; |
215 | wlNew.Set(wl); |
216 | if (*wordListN != wlNew) { |
217 | wordListN->Set(wl); |
218 | firstModification = 0; |
219 | } |
220 | } |
221 | return firstModification; |
222 | } |
223 | |
224 | void SCI_METHOD LexerABL::Lex(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) { |
225 | LexAccessor styler(pAccess); |
226 | |
227 | setWordStart = CharacterSet(CharacterSet::setAlpha, "_" , 0x80, true); |
228 | |
229 | int visibleChars = 0; |
230 | int visibleChars1 = 0; |
231 | int styleBeforeTaskMarker = SCE_ABL_DEFAULT; |
232 | bool continuationLine = false; |
233 | int = 0; |
234 | bool isSentenceStart = true; |
235 | bool possibleOOLChange = false; |
236 | |
237 | Sci_Position lineCurrent = styler.GetLine(startPos); |
238 | if (initStyle == SCE_ABL_PREPROCESSOR) { |
239 | // Set continuationLine if last character of previous line is '~' |
240 | if (lineCurrent > 0) { |
241 | Sci_Position endLinePrevious = styler.LineEnd(lineCurrent-1); |
242 | if (endLinePrevious > 0) { |
243 | continuationLine = styler.SafeGetCharAt(endLinePrevious-1) == '~'; |
244 | } |
245 | } |
246 | } |
247 | |
248 | // Look back to set variables that are actually invisible secondary states. The reason to avoid formal states is to cut down on state's bits |
249 | if (startPos > 0) { |
250 | Sci_Position back = startPos; |
251 | bool = (initStyle == SCE_ABL_COMMENT); |
252 | bool checkIsSentenceStart = (initStyle == SCE_ABL_DEFAULT || initStyle == SCE_ABL_IDENTIFIER); |
253 | char ch; |
254 | char st; |
255 | char chPrev; |
256 | char chPrev_1; |
257 | char chPrev_2; |
258 | char chPrev_3; |
259 | |
260 | while (back >= 0 && (checkCommentNestingLevel || checkIsSentenceStart)) { |
261 | ch = styler.SafeGetCharAt(back); |
262 | styler.Flush(); // looking at styles so need to flush |
263 | st = styler.StyleAt(back); |
264 | |
265 | chPrev = styler.SafeGetCharAt(back-1); |
266 | // isSentenceStart is a non-visible state, used to identify where statements and preprocessor declerations can start |
267 | if (checkIsSentenceStart && st != SCE_ABL_COMMENT && st != SCE_ABL_LINECOMMENT && st != SCE_ABL_CHARACTER && st != SCE_ABL_STRING ) { |
268 | chPrev_1 = styler.SafeGetCharAt(back-2); |
269 | chPrev_2 = styler.SafeGetCharAt(back-3); |
270 | chPrev_3 = styler.SafeGetCharAt(back-4); |
271 | if ((chPrev == '.' || chPrev == ':' || chPrev == '}' || |
272 | (chPrev_3 == 'e' && chPrev_2 == 'l' && chPrev_1 == 's' && chPrev == 'e') || |
273 | (chPrev_3 == 't' && chPrev_2 == 'h' && chPrev_1 == 'e' && chPrev == 'n')) && |
274 | (IsASpace(ch) || (ch == '/' && styler.SafeGetCharAt(back+1) == '*')) |
275 | ) { |
276 | checkIsSentenceStart = false; |
277 | isSentenceStart = true; |
278 | } |
279 | else if (IsASpace(chPrev) && ch == '{') { |
280 | checkIsSentenceStart = false; |
281 | isSentenceStart = false; |
282 | } |
283 | } |
284 | |
285 | // commentNestingLevel is a non-visible state, used to identify the nesting level of a comment |
286 | if (checkCommentNestingLevel) { |
287 | if (chPrev == '/' && ch == '*') { |
288 | commentNestingLevel++; |
289 | // eat the '/' so we don't miscount a */ if we see /*/* |
290 | --back; |
291 | } |
292 | if (chPrev == '*' && ch == '/') { |
293 | commentNestingLevel--; |
294 | // eat the '*' so we don't miscount a /* if we see */*/ |
295 | --back; |
296 | } |
297 | } |
298 | --back; |
299 | } |
300 | } |
301 | |
302 | StyleContext sc(startPos, length, initStyle, styler, static_cast<unsigned char>(0xff)); |
303 | Sci_Position lineEndNext = styler.LineEnd(lineCurrent); |
304 | |
305 | for (; sc.More();) { |
306 | if (sc.atLineStart) { |
307 | visibleChars = 0; |
308 | visibleChars1 = 0; |
309 | } |
310 | if (sc.atLineEnd) { |
311 | lineCurrent++; |
312 | lineEndNext = styler.LineEnd(lineCurrent); |
313 | } |
314 | // Handle line continuation generically. |
315 | if (sc.ch == '~') { |
316 | if (static_cast<Sci_Position>((sc.currentPos+1)) >= lineEndNext) { |
317 | lineCurrent++; |
318 | lineEndNext = styler.LineEnd(lineCurrent); |
319 | sc.Forward(); |
320 | if (sc.ch == '\r' && sc.chNext == '\n') { |
321 | sc.Forward(); |
322 | } |
323 | continuationLine = true; |
324 | sc.Forward(); |
325 | continue; |
326 | } |
327 | } |
328 | |
329 | const bool atLineEndBeforeSwitch = sc.atLineEnd; |
330 | // Determine if the current state should terminate. |
331 | switch (sc.state) { |
332 | case SCE_ABL_OPERATOR: |
333 | sc.SetState(SCE_ABL_DEFAULT); |
334 | break; |
335 | case SCE_ABL_NUMBER: |
336 | // We accept almost anything because of hex. and maybe number suffixes and scientific notations in the future |
337 | if (!(setWord.Contains(sc.ch) |
338 | || ((sc.ch == '+' || sc.ch == '-') && (sc.chPrev == 'e' || sc.chPrev == 'E' || |
339 | sc.chPrev == 'p' || sc.chPrev == 'P')))) { |
340 | sc.SetState(SCE_ABL_DEFAULT); |
341 | } |
342 | break; |
343 | case SCE_ABL_IDENTIFIER: |
344 | if (sc.atLineStart || sc.atLineEnd || (!setWord.Contains(sc.ch) && sc.ch != '-')) { |
345 | char s[1000]; |
346 | sc.GetCurrentLowered(s, sizeof(s)); |
347 | bool isLastWordEnd = (s[0] == 'e' && s[1] =='n' && s[2] == 'd' && !IsAlphaNumeric(s[3]) && s[3] != '-'); // helps to identify "end trigger" phrase |
348 | if ((isSentenceStart && keywords2.InListAbbreviated (s,'(')) || (!isLastWordEnd && keywords3.InListAbbreviated (s,'('))) { |
349 | sc.ChangeState(SCE_ABL_BLOCK); |
350 | isSentenceStart = false; |
351 | } |
352 | else if (keywords1.InListAbbreviated (s,'(')) { |
353 | if (isLastWordEnd || |
354 | (s[0] == 'f' && s[1] =='o' && s[2] == 'r' && s[3] == 'w' && s[4] =='a' && s[5] == 'r' && s[6] == 'd'&& !IsAlphaNumeric(s[7]))) { |
355 | sc.ChangeState(SCE_ABL_END); |
356 | isSentenceStart = false; |
357 | } |
358 | else if ((s[0] == 'e' && s[1] =='l' && s[2] == 's' && s[3] == 'e') || |
359 | (s[0] == 't' && s[1] =='h' && s[2] == 'e' && s[3] == 'n')) { |
360 | sc.ChangeState(SCE_ABL_WORD); |
361 | isSentenceStart = true; |
362 | } |
363 | else { |
364 | sc.ChangeState(SCE_ABL_WORD); |
365 | isSentenceStart = false; |
366 | } |
367 | } |
368 | sc.SetState(SCE_ABL_DEFAULT); |
369 | } |
370 | break; |
371 | case SCE_ABL_PREPROCESSOR: |
372 | if (sc.atLineStart && !continuationLine) { |
373 | sc.SetState(SCE_ABL_DEFAULT); |
374 | // Force Scintilla to acknowledge changed stated even though this change might happen outside of the current line |
375 | possibleOOLChange = true; |
376 | isSentenceStart = true; |
377 | } |
378 | break; |
379 | case SCE_ABL_LINECOMMENT: |
380 | if (sc.atLineStart && !continuationLine) { |
381 | sc.SetState(SCE_ABL_DEFAULT); |
382 | isSentenceStart = true; |
383 | } else { |
384 | styleBeforeTaskMarker = SCE_ABL_LINECOMMENT; |
385 | highlightTaskMarker(sc, styler, keywords4); |
386 | } |
387 | break; |
388 | case SCE_ABL_TASKMARKER: |
389 | if (isoperator(sc.ch) || IsASpace(sc.ch)) { |
390 | sc.SetState(styleBeforeTaskMarker); |
391 | styleBeforeTaskMarker = SCE_ABL_DEFAULT; |
392 | } |
393 | // fall through |
394 | case SCE_ABL_COMMENT: |
395 | if (sc.Match('*', '/')) { |
396 | sc.Forward(); |
397 | commentNestingLevel--; |
398 | if (commentNestingLevel == 0) { |
399 | sc.ForwardSetState(SCE_ABL_DEFAULT); |
400 | possibleOOLChange = true; |
401 | } |
402 | } else if (sc.Match('/', '*')) { |
403 | commentNestingLevel++; |
404 | sc.Forward(); |
405 | } |
406 | if (commentNestingLevel > 0) { |
407 | styleBeforeTaskMarker = SCE_ABL_COMMENT; |
408 | possibleOOLChange = true; |
409 | highlightTaskMarker(sc, styler, keywords4); |
410 | } |
411 | break; |
412 | case SCE_ABL_STRING: |
413 | if (sc.ch == '~') { |
414 | sc.Forward(); // Skip a character after a tilde |
415 | } else if (sc.ch == '\"') { |
416 | sc.ForwardSetState(SCE_ABL_DEFAULT); |
417 | } |
418 | break; |
419 | case SCE_ABL_CHARACTER: |
420 | if (sc.ch == '~') { |
421 | sc.Forward(); // Skip a character after a tilde |
422 | } else if (sc.ch == '\'') { |
423 | sc.ForwardSetState(SCE_ABL_DEFAULT); |
424 | } |
425 | break; |
426 | } |
427 | |
428 | if (sc.atLineEnd && !atLineEndBeforeSwitch) { |
429 | // State exit processing consumed characters up to end of line. |
430 | lineCurrent++; |
431 | lineEndNext = styler.LineEnd(lineCurrent); |
432 | } |
433 | |
434 | // Determine if a new state should be entered. |
435 | if (sc.state == SCE_ABL_DEFAULT) { |
436 | if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) { |
437 | sc.SetState(SCE_ABL_NUMBER); |
438 | isSentenceStart = false; |
439 | } else if (!sc.atLineEnd && (setWordStart.Contains(sc.ch)) && sc.chPrev != '&') { |
440 | sc.SetState(SCE_ABL_IDENTIFIER); |
441 | } else if (sc.Match('/', '*')) { |
442 | if (sc.chPrev == '.' || sc.chPrev == ':' || sc.chPrev == '}') { |
443 | isSentenceStart = true; |
444 | } |
445 | sc.SetState(SCE_ABL_COMMENT); |
446 | possibleOOLChange = true; |
447 | commentNestingLevel++; |
448 | sc.Forward(); // Eat the * so it isn't used for the end of the comment |
449 | } else if (sc.ch == '\"') { |
450 | sc.SetState(SCE_ABL_STRING); |
451 | isSentenceStart = false; |
452 | } else if (sc.ch == '\'') { |
453 | sc.SetState(SCE_ABL_CHARACTER); |
454 | isSentenceStart = false; |
455 | } else if (sc.ch == '&' && visibleChars1 == 0 && isSentenceStart) { |
456 | // Preprocessor commands are alone on their line |
457 | sc.SetState(SCE_ABL_PREPROCESSOR); |
458 | // Force Scintilla to acknowledge changed stated even though this change might happen outside of the current line |
459 | possibleOOLChange = true; |
460 | // Skip whitespace between & and preprocessor word |
461 | do { |
462 | sc.Forward(); |
463 | } while ((sc.ch == ' ' || sc.ch == '\t') && sc.More()); |
464 | if (sc.atLineEnd) { |
465 | sc.SetState(SCE_ABL_DEFAULT); |
466 | } |
467 | } else if (sc.Match('/','/') && (IsASpace(sc.chPrev) || isSentenceStart)) { |
468 | // Line comments are valid after a white space or EOL |
469 | sc.SetState(SCE_ABL_LINECOMMENT); |
470 | // Skip whitespace between // and preprocessor word |
471 | do { |
472 | sc.Forward(); |
473 | } while ((sc.ch == ' ' || sc.ch == '\t') && sc.More()); |
474 | if (sc.atLineEnd) { |
475 | sc.SetState(SCE_ABL_DEFAULT); |
476 | } |
477 | } else if (isoperator(sc.ch)) { |
478 | sc.SetState(SCE_ABL_OPERATOR); |
479 | /* This code allows highlight of handles. Alas, it would cause the phrase "last-event:function" |
480 | to be recognized as a BlockBegin */ |
481 | isSentenceStart = false; |
482 | } |
483 | else if ((sc.chPrev == '.' || sc.chPrev == ':' || sc.chPrev == '}') && (IsASpace(sc.ch))) { |
484 | isSentenceStart = true; |
485 | } |
486 | } |
487 | if (!IsASpace(sc.ch)) { |
488 | visibleChars1++; |
489 | } |
490 | if (!IsASpace(sc.ch) && !IsSpaceEquiv(sc.state)) { |
491 | visibleChars++; |
492 | } |
493 | continuationLine = false; |
494 | sc.Forward(); |
495 | } |
496 | if (possibleOOLChange) |
497 | styler.ChangeLexerState(startPos, startPos + length); |
498 | sc.Complete(); |
499 | } |
500 | |
501 | |
502 | // Store both the current line's fold level and the next lines in the |
503 | // level store to make it easy to pick up with each increment |
504 | // and to make it possible to fiddle the current level for "} else {". |
505 | |
506 | void SCI_METHOD LexerABL::Fold(Sci_PositionU startPos, Sci_Position length, int initStyle, IDocument *pAccess) { |
507 | |
508 | if (!options.fold) |
509 | return; |
510 | |
511 | LexAccessor styler(pAccess); |
512 | |
513 | Sci_PositionU endPos = startPos + length; |
514 | int visibleChars = 0; |
515 | Sci_Position lineCurrent = styler.GetLine(startPos); |
516 | int levelCurrent = SC_FOLDLEVELBASE; |
517 | if (lineCurrent > 0) |
518 | levelCurrent = styler.LevelAt(lineCurrent-1) >> 16; |
519 | Sci_PositionU lineStartNext = styler.LineStart(lineCurrent+1); |
520 | int levelNext = levelCurrent; |
521 | char chNext = styler[startPos]; |
522 | int styleNext = styler.StyleAt(startPos); |
523 | int style = initStyle; |
524 | for (Sci_PositionU i = startPos; i < endPos; i++) { |
525 | chNext = static_cast<char>(tolower(chNext)); // check tolower |
526 | char ch = chNext; |
527 | chNext = styler.SafeGetCharAt(i+1); |
528 | int stylePrev = style; |
529 | style = styleNext; |
530 | styleNext = styler.StyleAt(i+1); |
531 | bool atEOL = i == (lineStartNext-1); |
532 | if (options.foldComment && options.foldCommentMultiline && IsStreamCommentStyle(style)) { |
533 | if (!IsStreamCommentStyle(stylePrev)) { |
534 | levelNext++; |
535 | } else if (!IsStreamCommentStyle(styleNext) && !atEOL) { |
536 | // Comments don't end at end of line and the next character may be unstyled. |
537 | levelNext--; |
538 | } |
539 | } |
540 | if (options.foldSyntaxBased) { |
541 | if (style == SCE_ABL_BLOCK && !IsAlphaNumeric(chNext)) { |
542 | levelNext++; |
543 | } |
544 | else if (style == SCE_ABL_END && (ch == 'e' || ch == 'f')) { |
545 | levelNext--; |
546 | } |
547 | } |
548 | if (!IsASpace(ch)) |
549 | visibleChars++; |
550 | if (atEOL || (i == endPos-1)) { |
551 | int lev = levelCurrent | levelNext << 16; |
552 | if (visibleChars == 0 && options.foldCompact) |
553 | lev |= SC_FOLDLEVELWHITEFLAG; |
554 | if (levelCurrent < levelNext) |
555 | lev |= SC_FOLDLEVELHEADERFLAG; |
556 | if (lev != styler.LevelAt(lineCurrent)) { |
557 | styler.SetLevel(lineCurrent, lev); |
558 | } |
559 | lineCurrent++; |
560 | lineStartNext = styler.LineStart(lineCurrent+1); |
561 | levelCurrent = levelNext; |
562 | if (atEOL && (i == static_cast<Sci_PositionU>(styler.Length()-1))) { |
563 | // There is an empty line at end of file so give it same level and empty |
564 | styler.SetLevel(lineCurrent, (levelCurrent | levelCurrent << 16) | SC_FOLDLEVELWHITEFLAG); |
565 | } |
566 | visibleChars = 0; |
567 | } |
568 | } |
569 | } |
570 | |
571 | LexerModule lmProgress(SCLEX_PROGRESS, LexerABL::LexerFactoryABL, "abl" , ablWordLists); |
572 | |