1 // Scintilla source code edit control
2 // @file LexPowerPro.cxx
3 // PowerPro utility, written by Bruce Switzer, is available from http://powerpro.webeddie.com
4 // PowerPro lexer is written by Christopher Bean (cbean@cb-software.net)
6 // Lexer code heavily borrowed from:
7 // LexAU3.cxx by Jos van der Zande
8 // LexCPP.cxx by Neil Hodgson
9 // LexVB.cxx by Neil Hodgson
12 // 2008-10-25 - Initial release
13 // 2008-10-26 - Changed how <name> is hilighted in 'function <name>' so that
14 // local isFunction = "" and local functions = "" don't get falsely highlighted
15 // 2008-12-14 - Added bounds checking for szKeyword and szDo
16 // - Replaced SetOfCharacters with CharacterSet
17 // - Made sure that CharacterSet::Contains is passed only positive values
18 // - Made sure that the return value of Accessor::SafeGetCharAt is positive before
19 // passsing to functions that require positive values like isspacechar()
20 // - Removed unused visibleChars processing from ColourisePowerProDoc()
21 // - Fixed bug with folding logic where line continuations didn't end where
22 // they were supposed to
23 // - Moved all helper functions to the top of the file
25 // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
26 // The License.txt file describes the conditions under which this software may be distributed.
37 #include "StyleContext.h"
39 #include "Scintilla.h"
41 #include "CharacterSet.h"
44 using namespace Scintilla
;
47 static inline bool IsStreamCommentStyle(int style
) {
48 return style
== SCE_POWERPRO_COMMENTBLOCK
;
51 static bool IsContinuationLine(unsigned int szLine
, Accessor
&styler
)
53 int nsPos
= styler
.LineStart(szLine
);
54 int nePos
= styler
.LineStart(szLine
+ 1) - 2;
57 int stylech
= styler
.StyleAt(nsPos
);
58 if (!(stylech
== SCE_POWERPRO_COMMENTBLOCK
)) {
59 char ch
= styler
.SafeGetCharAt(nePos
);
60 char chPrev
= styler
.SafeGetCharAt(nePos
-1);
61 char chPrevPrev
= styler
.SafeGetCharAt(nePos
-2);
62 if (ch
> 0 && chPrev
> 0 && chPrevPrev
> 0 && !isspacechar(ch
) && !isspacechar(chPrev
) && !isspacechar(chPrevPrev
) ) {
63 if (chPrevPrev
== ';' && chPrev
== ';' && ch
== '+')
69 nePos
--; // skip to next char
74 // Routine to find first none space on the current line and return its Style
75 // needed for comment lines not starting on pos 1
76 static int GetStyleFirstWord(unsigned int szLine
, Accessor
&styler
)
78 int nsPos
= styler
.LineStart(szLine
);
79 int nePos
= styler
.LineStart(szLine
+1) - 1;
80 char ch
= styler
.SafeGetCharAt(nsPos
);
82 while (ch
> 0 && isspacechar(ch
) && nsPos
< nePos
)
84 nsPos
++; // skip to next char
85 ch
= styler
.SafeGetCharAt(nsPos
);
88 return styler
.StyleAt(nsPos
);
91 //returns true if there is a function to highlight
92 //used to highlight <name> in 'function <name>'
93 static bool HasFunction(Accessor
&styler
, unsigned int currentPos
) {
95 //check for presence of 'function '
96 return (styler
.SafeGetCharAt(currentPos
) == ' '
97 && tolower(styler
.SafeGetCharAt(currentPos
-1)) == 'n'
98 && tolower(styler
.SafeGetCharAt(currentPos
-2)) == 'o'
99 && tolower(styler
.SafeGetCharAt(currentPos
-3)) == 'i'
100 && tolower(styler
.SafeGetCharAt(currentPos
-4)) == 't'
101 && tolower(styler
.SafeGetCharAt(currentPos
-5)) == 'c'
102 && tolower(styler
.SafeGetCharAt(currentPos
-6)) == 'n'
103 && tolower(styler
.SafeGetCharAt(currentPos
-7)) == 'u'
104 && tolower(styler
.SafeGetCharAt(currentPos
-8)) == 'f'
105 //only allow 'function ' to appear at the beginning of a line
106 && (styler
.SafeGetCharAt(currentPos
-9) == '\n'
107 || styler
.SafeGetCharAt(currentPos
-9) == '\r'
108 || (styler
.SafeGetCharAt(currentPos
-9, '\0')) == '\0') //is the first line
112 static void ColourisePowerProDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
113 Accessor
&styler
, bool caseSensitive
) {
115 WordList
&keywords
= *keywordlists
[0];
116 WordList
&keywords2
= *keywordlists
[1];
117 WordList
&keywords3
= *keywordlists
[2];
118 WordList
&keywords4
= *keywordlists
[3];
120 //define the character sets
121 CharacterSet
setWordStart(CharacterSet::setAlpha
, "_@", 0x80, true);
122 CharacterSet
setWord(CharacterSet::setAlphaNum
, "._", 0x80, true);
124 StyleContext
sc(startPos
, length
, initStyle
, styler
);
125 char s_save
[100]; //for last line highlighting
127 for (; sc
.More(); sc
.Forward()) {
129 // **********************************************
130 // save the total current word for eof processing
132 sc
.GetCurrentLowered(s
, sizeof(s
));
134 if ((sc
.ch
> 0) && setWord
.Contains(sc
.ch
))
137 int tp
= strlen(s_save
);
139 s_save
[tp
] = static_cast<char>(tolower(sc
.ch
));
143 // **********************************************
146 if (sc
.atLineStart
) {
147 if (sc
.state
== SCE_POWERPRO_DOUBLEQUOTEDSTRING
) {
148 // Prevent SCE_POWERPRO_STRINGEOL from leaking back to previous line which
149 // ends with a line continuation by locking in the state upto this position.
150 sc
.SetState(SCE_POWERPRO_DOUBLEQUOTEDSTRING
);
154 // Determine if the current state should terminate.
156 case SCE_POWERPRO_OPERATOR
:
157 sc
.SetState(SCE_POWERPRO_DEFAULT
);
160 case SCE_POWERPRO_NUMBER
:
162 if (!IsADigit(sc
.ch
))
163 sc
.SetState(SCE_POWERPRO_DEFAULT
);
167 case SCE_POWERPRO_IDENTIFIER
:
168 //if ((sc.ch > 0) && !setWord.Contains(sc.ch) || (sc.ch == '.')) { // use this line if don't want to match keywords with . in them. ie: win.debug will match both win and debug so win debug will also be colorized
169 if ((sc
.ch
> 0) && !setWord
.Contains(sc
.ch
)){ // || (sc.ch == '.')) { // use this line if you want to match keywords with a . ie: win.debug will only match win.debug neither win nor debug will be colorized separately
172 sc
.GetCurrent(s
, sizeof(s
));
174 sc
.GetCurrentLowered(s
, sizeof(s
));
176 if (keywords
.InList(s
)) {
177 sc
.ChangeState(SCE_POWERPRO_WORD
);
178 } else if (keywords2
.InList(s
)) {
179 sc
.ChangeState(SCE_POWERPRO_WORD2
);
180 } else if (keywords3
.InList(s
)) {
181 sc
.ChangeState(SCE_POWERPRO_WORD3
);
182 } else if (keywords4
.InList(s
)) {
183 sc
.ChangeState(SCE_POWERPRO_WORD4
);
185 sc
.SetState(SCE_POWERPRO_DEFAULT
);
189 case SCE_POWERPRO_LINECONTINUE
:
190 if (sc
.atLineStart
) {
191 sc
.SetState(SCE_POWERPRO_DEFAULT
);
192 } else if (sc
.Match('/', '*') || sc
.Match('/', '/')) {
193 sc
.SetState(SCE_POWERPRO_DEFAULT
);
197 case SCE_POWERPRO_COMMENTBLOCK
:
198 if (sc
.Match('*', '/')) {
200 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
204 case SCE_POWERPRO_COMMENTLINE
:
205 if (sc
.atLineStart
) {
206 sc
.SetState(SCE_POWERPRO_DEFAULT
);
210 case SCE_POWERPRO_DOUBLEQUOTEDSTRING
:
212 sc
.ChangeState(SCE_POWERPRO_STRINGEOL
);
213 } else if (sc
.ch
== '\\') {
214 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
217 } else if (sc
.ch
== '\"') {
218 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
222 case SCE_POWERPRO_SINGLEQUOTEDSTRING
:
224 sc
.ChangeState(SCE_POWERPRO_STRINGEOL
);
225 } else if (sc
.ch
== '\\') {
226 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
229 } else if (sc
.ch
== '\'') {
230 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
234 case SCE_POWERPRO_STRINGEOL
:
235 if (sc
.atLineStart
) {
236 sc
.SetState(SCE_POWERPRO_DEFAULT
);
240 case SCE_POWERPRO_VERBATIM
:
242 if (sc
.chNext
== '\"') {
245 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
250 case SCE_POWERPRO_ALTQUOTE
:
252 if (sc
.chNext
== '#') {
255 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
260 case SCE_POWERPRO_FUNCTION
:
261 if (sc
.ch
== '\r' || sc
.ch
== '\n' || sc
.ch
== ' ' || sc
.ch
== '(') {
262 sc
.SetState(SCE_POWERPRO_DEFAULT
);
267 // Determine if a new state should be entered.
268 if (sc
.state
== SCE_POWERPRO_DEFAULT
) {
269 if (sc
.Match('?', '\"')) {
270 sc
.SetState(SCE_POWERPRO_VERBATIM
);
272 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
273 sc
.SetState(SCE_POWERPRO_NUMBER
);
274 }else if (sc
.Match('?','#')) {
275 if (sc
.ch
== '?' && sc
.chNext
== '#') {
276 sc
.SetState(SCE_POWERPRO_ALTQUOTE
);
279 } else if (HasFunction(styler
, sc
.currentPos
)) { //highlight <name> in 'function <name>'
280 sc
.SetState(SCE_POWERPRO_FUNCTION
);
281 } else if (sc
.ch
== '@' && sc
.atLineStart
) { //alternate function definition [label]
282 sc
.SetState(SCE_POWERPRO_FUNCTION
);
283 } else if ((sc
.ch
> 0) && (setWordStart
.Contains(sc
.ch
) || (sc
.ch
== '?'))) {
284 sc
.SetState(SCE_POWERPRO_IDENTIFIER
);
285 } else if (sc
.Match(";;+")) {
286 sc
.SetState(SCE_POWERPRO_LINECONTINUE
);
287 } else if (sc
.Match('/', '*')) {
288 sc
.SetState(SCE_POWERPRO_COMMENTBLOCK
);
289 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
290 } else if (sc
.Match('/', '/')) {
291 sc
.SetState(SCE_POWERPRO_COMMENTLINE
);
292 } else if (sc
.atLineStart
&& sc
.ch
== ';') { //legacy comment that can only appear at the beginning of a line
293 sc
.SetState(SCE_POWERPRO_COMMENTLINE
);
294 } else if (sc
.Match(";;")) {
295 sc
.SetState(SCE_POWERPRO_COMMENTLINE
);
296 } else if (sc
.ch
== '\"') {
297 sc
.SetState(SCE_POWERPRO_DOUBLEQUOTEDSTRING
);
298 } else if (sc
.ch
== '\'') {
299 sc
.SetState(SCE_POWERPRO_SINGLEQUOTEDSTRING
);
300 } else if (isoperator(static_cast<char>(sc
.ch
))) {
301 sc
.SetState(SCE_POWERPRO_OPERATOR
);
306 //*************************************
307 // Colourize the last word correctly
308 //*************************************
309 if (sc
.state
== SCE_POWERPRO_IDENTIFIER
)
311 if (keywords
.InList(s_save
)) {
312 sc
.ChangeState(SCE_POWERPRO_WORD
);
313 sc
.SetState(SCE_POWERPRO_DEFAULT
);
315 else if (keywords2
.InList(s_save
)) {
316 sc
.ChangeState(SCE_POWERPRO_WORD2
);
317 sc
.SetState(SCE_POWERPRO_DEFAULT
);
319 else if (keywords3
.InList(s_save
)) {
320 sc
.ChangeState(SCE_POWERPRO_WORD3
);
321 sc
.SetState(SCE_POWERPRO_DEFAULT
);
323 else if (keywords4
.InList(s_save
)) {
324 sc
.ChangeState(SCE_POWERPRO_WORD4
);
325 sc
.SetState(SCE_POWERPRO_DEFAULT
);
328 sc
.SetState(SCE_POWERPRO_DEFAULT
);
334 static void FoldPowerProDoc(unsigned int startPos
, int length
, int, WordList
*[], Accessor
&styler
)
336 //define the character sets
337 CharacterSet
setWordStart(CharacterSet::setAlpha
, "_@", 0x80, true);
338 CharacterSet
setWord(CharacterSet::setAlphaNum
, "._", 0x80, true);
340 bool isFoldingAll
= true; //used to tell if we're recursively folding the whole document, or just a small piece (ie: if statement or 1 function)
341 int endPos
= startPos
+ length
;
342 int lastLine
= styler
.GetLine(styler
.Length()); //used to help fold the last line correctly
344 // get settings from the config files for folding comments and preprocessor lines
345 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
346 bool foldInComment
= styler
.GetPropertyInt("fold.comment") == 2;
347 bool foldCompact
= true;
349 // Backtrack to previous line in case need to fix its fold status
350 int lineCurrent
= styler
.GetLine(startPos
);
352 isFoldingAll
= false;
353 if (lineCurrent
> 0) {
355 startPos
= styler
.LineStart(lineCurrent
);
358 // vars for style of previous/current/next lines
359 int style
= GetStyleFirstWord(lineCurrent
,styler
);
362 // find the first previous line without continuation character at the end
363 while ((lineCurrent
> 0 && IsContinuationLine(lineCurrent
,styler
)) ||
364 (lineCurrent
> 1 && IsContinuationLine(lineCurrent
-1,styler
))) {
366 startPos
= styler
.LineStart(lineCurrent
);
368 if (lineCurrent
> 0) {
369 stylePrev
= GetStyleFirstWord(lineCurrent
-1,styler
);
371 // vars for getting first word to check for keywords
372 bool FirstWordStart
= false;
373 bool FirstWordEnd
= false;
375 const unsigned int KEYWORD_MAX
= 10;
376 char szKeyword
[KEYWORD_MAX
]="";
377 unsigned int szKeywordlen
= 0;
381 bool DoFoundLast
= false;
383 // var for indentlevel
384 int levelCurrent
= SC_FOLDLEVELBASE
;
385 if (lineCurrent
> 0) {
386 levelCurrent
= styler
.LevelAt(lineCurrent
-1) >> 16;
388 int levelNext
= levelCurrent
;
390 int visibleChars
= 0;
391 int functionCount
= 0;
393 char chNext
= styler
.SafeGetCharAt(startPos
);
395 char chPrevPrev
= '\0';
396 char chPrevPrevPrev
= '\0';
398 for (int i
= startPos
; i
< endPos
; i
++) {
401 chNext
= styler
.SafeGetCharAt(i
+ 1);
403 if ((ch
> 0) && setWord
.Contains(ch
)) {
407 // get the syle for the current character neede to check in comment
408 int stylech
= styler
.StyleAt(i
);
410 // get first word for the line for indent check max 9 characters
411 if (FirstWordStart
&& (!(FirstWordEnd
))) {
412 if ((ch
> 0) && !setWord
.Contains(ch
)) {
415 else if (szKeywordlen
< KEYWORD_MAX
- 1) {
416 szKeyword
[szKeywordlen
++] = static_cast<char>(tolower(ch
));
417 szKeyword
[szKeywordlen
] = '\0';
421 // start the capture of the first word
422 if (!(FirstWordStart
)) {
423 if ((ch
> 0) && (setWord
.Contains(ch
) || setWordStart
.Contains(ch
) || ch
== ';' || ch
== '/')) {
424 FirstWordStart
= true;
425 if (szKeywordlen
< KEYWORD_MAX
- 1) {
426 szKeyword
[szKeywordlen
++] = static_cast<char>(tolower(ch
));
427 szKeyword
[szKeywordlen
] = '\0';
431 // only process this logic when not in comment section
432 if (stylech
!= SCE_POWERPRO_COMMENTLINE
) {
434 if (DoFoundLast
&& (ch
> 0) && setWord
.Contains(ch
)) {
438 // find out if the word "do" is the last on a "if" line
439 if (FirstWordEnd
&& strcmp(szKeyword
,"if") == 0) {
442 szDo
[1] = static_cast<char>(tolower(ch
));
444 if (strcmp(szDo
,"do") == 0 ) {
448 else if (szDolen
< 2) {
449 szDo
[szDolen
++] = static_cast<char>(tolower(ch
));
450 szDo
[szDolen
] = '\0';
455 // End of Line found so process the information
456 if ((ch
== '\r' && chNext
!= '\n') || (ch
== '\n') || (i
== endPos
)) {
458 // **************************
459 // Folding logic for Keywords
460 // **************************
462 // if a keyword is found on the current line and the line doesn't end with ;;+ (continuation)
463 // and we are not inside a commentblock.
464 if (szKeywordlen
> 0 &&
465 (!(chPrev
== '+' && chPrevPrev
== ';' && chPrevPrevPrev
==';')) &&
466 ((!(IsStreamCommentStyle(style
)) || foldInComment
)) ) {
468 // only fold "if" last keyword is "then" (else its a one line if)
469 if (strcmp(szKeyword
,"if") == 0 && DoFoundLast
) {
472 // create new fold for these words
473 if (strcmp(szKeyword
,"for") == 0) {
477 //handle folding for functions/labels
478 //Note: Functions and labels don't have an explicit end like [end function]
479 // 1. functions/labels end at the start of another function
480 // 2. functions/labels end at the end of the file
481 if ((strcmp(szKeyword
,"function") == 0) || (szKeywordlen
> 0 && szKeyword
[0] == '@')) {
482 if (isFoldingAll
) { //if we're folding the whole document (recursivly by lua script)
484 if (functionCount
> 0) {
491 } else { //if just folding a small piece (by clicking on the minus sign next to the word)
496 // end the fold for these words before the current line
497 if (strcmp(szKeyword
,"endif") == 0 || strcmp(szKeyword
,"endfor") == 0) {
501 // end the fold for these words before the current line and Start new fold
502 if (strcmp(szKeyword
,"else") == 0 || strcmp(szKeyword
,"elseif") == 0 ) {
506 // Preprocessor and Comment folding
507 int styleNext
= GetStyleFirstWord(lineCurrent
+ 1,styler
);
509 // *********************************
510 // Folding logic for Comment blocks
511 // *********************************
512 if (foldComment
&& IsStreamCommentStyle(style
)) {
513 // Start of a comment block
514 if (!(stylePrev
==style
) && IsStreamCommentStyle(styleNext
) && styleNext
==style
) {
517 // fold till the last line for normal comment lines
518 else if (IsStreamCommentStyle(stylePrev
)
519 && !(styleNext
== SCE_POWERPRO_COMMENTLINE
)
520 && stylePrev
== SCE_POWERPRO_COMMENTLINE
521 && style
== SCE_POWERPRO_COMMENTLINE
) {
524 // fold till the one but last line for Blockcomment lines
525 else if (IsStreamCommentStyle(stylePrev
)
526 && !(styleNext
== SCE_POWERPRO_COMMENTBLOCK
)
527 && style
== SCE_POWERPRO_COMMENTBLOCK
) {
533 int levelUse
= levelCurrent
;
534 int lev
= levelUse
| levelNext
<< 16;
535 if (visibleChars
== 0 && foldCompact
)
536 lev
|= SC_FOLDLEVELWHITEFLAG
;
537 if (levelUse
< levelNext
) {
538 lev
|= SC_FOLDLEVELHEADERFLAG
;
540 if (lev
!= styler
.LevelAt(lineCurrent
)) {
541 styler
.SetLevel(lineCurrent
, lev
);
544 // reset values for the next line
548 levelCurrent
= levelNext
;
551 // if the last characters are ;;+ then don't reset since the line continues on the next line.
552 if (chPrev
== '+' && chPrevPrev
== ';' && chPrevPrevPrev
== ';') {
557 FirstWordStart
= false;
558 FirstWordEnd
= false;
561 for (unsigned int i
= 0; i
< KEYWORD_MAX
; i
++) {
567 // save the last processed characters
568 if ((ch
> 0) && !isspacechar(ch
)) {
569 chPrevPrevPrev
= chPrevPrev
;
576 //close folds on the last line - without this a 'phantom'
577 //fold can appear when an open fold is on the last line
578 //this can occur because functions and labels don't have an explicit end
579 if (lineCurrent
>= lastLine
) {
581 lev
|= SC_FOLDLEVELWHITEFLAG
;
582 styler
.SetLevel(lineCurrent
, lev
);
587 static const char * const powerProWordLists
[] = {
595 static void ColourisePowerProDocWrapper(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
597 ColourisePowerProDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
600 LexerModule
lmPowerPro(SCLEX_POWERPRO
, ColourisePowerProDocWrapper
, "powerpro", FoldPowerProDoc
, powerProWordLists
);