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 szFirstWord 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 // passing 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
24 // 2010-06-03 - Added onlySpaces variable to allow the @function and ;comment styles to be indented
25 // - Modified HasFunction function to be a bit more robust
26 // - Renamed HasFunction function to IsFunction
28 // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
29 // The License.txt file describes the conditions under which this software may be distributed.
36 #include "Scintilla.h"
40 #include "LexAccessor.h"
42 #include "StyleContext.h"
43 #include "CharacterSet.h"
44 #include "LexerModule.h"
47 using namespace Scintilla
;
50 static inline bool IsStreamCommentStyle(int style
) {
51 return style
== SCE_POWERPRO_COMMENTBLOCK
;
54 static inline bool IsLineEndChar(unsigned char ch
) {
55 return ch
== 0x0a //LF
60 static bool IsContinuationLine(unsigned int szLine
, Accessor
&styler
)
62 int startPos
= styler
.LineStart(szLine
);
63 int endPos
= styler
.LineStart(szLine
+ 1) - 2;
64 while (startPos
< endPos
)
66 char stylech
= styler
.StyleAt(startPos
);
67 if (!(stylech
== SCE_POWERPRO_COMMENTBLOCK
)) {
68 char ch
= styler
.SafeGetCharAt(endPos
);
69 char chPrev
= styler
.SafeGetCharAt(endPos
- 1);
70 char chPrevPrev
= styler
.SafeGetCharAt(endPos
- 2);
71 if (ch
> 0 && chPrev
> 0 && chPrevPrev
> 0 && !isspacechar(ch
) && !isspacechar(chPrev
) && !isspacechar(chPrevPrev
) )
72 return (chPrevPrev
== ';' && chPrev
== ';' && ch
== '+');
74 endPos
--; // skip to next char
79 // Routine to find first none space on the current line and return its Style
80 // needed for comment lines not starting on pos 1
81 static int GetStyleFirstWord(int szLine
, Accessor
&styler
)
83 int startPos
= styler
.LineStart(szLine
);
84 int endPos
= styler
.LineStart(szLine
+ 1) - 1;
85 char ch
= styler
.SafeGetCharAt(startPos
);
87 while (ch
> 0 && isspacechar(ch
) && startPos
< endPos
)
89 startPos
++; // skip to next char
90 ch
= styler
.SafeGetCharAt(startPos
);
92 return styler
.StyleAt(startPos
);
95 //returns true if there is a function to highlight
96 //used to highlight <name> in 'function <name>'
98 // sample line (without quotes): "\tfunction asdf()
99 // currentPos will be the position of 'a'
100 static bool IsFunction(Accessor
&styler
, unsigned int currentPos
) {
102 const char function
[10] = "function "; //10 includes \0
103 unsigned int numberOfCharacters
= sizeof(function
) - 1;
104 unsigned int position
= currentPos
- numberOfCharacters
;
106 //compare each character with the letters in the function array
107 //return false if ALL don't match
108 for (unsigned int i
= 0; i
< numberOfCharacters
; i
++) {
109 char c
= styler
.SafeGetCharAt(position
++);
110 if (c
!= function
[i
])
114 //make sure that there are only spaces (or tabs) between the beginning
115 //of the line and the function declaration
116 position
= currentPos
- numberOfCharacters
- 1; //-1 to move to char before 'function'
117 for (unsigned int j
= 0; j
< 16; j
++) { //check up to 16 preceeding characters
118 char c
= styler
.SafeGetCharAt(position
--, '\0'); //if can't read char, return NUL (past beginning of document)
119 if (c
<= 0) //reached beginning of document
121 if (c
> 0 && IsLineEndChar(c
))
123 else if (c
> 0 && !IsASpaceOrTab(c
))
131 static void ColourisePowerProDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
132 Accessor
&styler
, bool caseSensitive
) {
134 WordList
&keywords
= *keywordlists
[0];
135 WordList
&keywords2
= *keywordlists
[1];
136 WordList
&keywords3
= *keywordlists
[2];
137 WordList
&keywords4
= *keywordlists
[3];
139 //define the character sets
140 CharacterSet
setWordStart(CharacterSet::setAlpha
, "_@", 0x80, true);
141 CharacterSet
setWord(CharacterSet::setAlphaNum
, "._", 0x80, true);
143 StyleContext
sc(startPos
, length
, initStyle
, styler
);
144 char s_save
[100]; //for last line highlighting
146 //are there only spaces between the first letter of the line and the beginning of the line
147 bool onlySpaces
= true;
149 for (; sc
.More(); sc
.Forward()) {
151 // save the total current word for eof processing
153 sc
.GetCurrentLowered(s
, sizeof(s
));
155 if ((sc
.ch
> 0) && setWord
.Contains(sc
.ch
))
158 int tp
= static_cast<int>(strlen(s_save
));
160 s_save
[tp
] = static_cast<char>(tolower(sc
.ch
));
165 if (sc
.atLineStart
) {
166 if (sc
.state
== SCE_POWERPRO_DOUBLEQUOTEDSTRING
) {
167 // Prevent SCE_POWERPRO_STRINGEOL from leaking back to previous line which
168 // ends with a line continuation by locking in the state upto this position.
169 sc
.SetState(SCE_POWERPRO_DOUBLEQUOTEDSTRING
);
173 // Determine if the current state should terminate.
175 case SCE_POWERPRO_OPERATOR
:
176 sc
.SetState(SCE_POWERPRO_DEFAULT
);
179 case SCE_POWERPRO_NUMBER
:
181 if (!IsADigit(sc
.ch
))
182 sc
.SetState(SCE_POWERPRO_DEFAULT
);
186 case SCE_POWERPRO_IDENTIFIER
:
187 //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
188 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
191 sc
.GetCurrent(s
, sizeof(s
));
193 sc
.GetCurrentLowered(s
, sizeof(s
));
196 if (keywords
.InList(s
)) {
197 sc
.ChangeState(SCE_POWERPRO_WORD
);
198 } else if (keywords2
.InList(s
)) {
199 sc
.ChangeState(SCE_POWERPRO_WORD2
);
200 } else if (keywords3
.InList(s
)) {
201 sc
.ChangeState(SCE_POWERPRO_WORD3
);
202 } else if (keywords4
.InList(s
)) {
203 sc
.ChangeState(SCE_POWERPRO_WORD4
);
205 sc
.SetState(SCE_POWERPRO_DEFAULT
);
209 case SCE_POWERPRO_LINECONTINUE
:
210 if (sc
.atLineStart
) {
211 sc
.SetState(SCE_POWERPRO_DEFAULT
);
212 } else if (sc
.Match('/', '*') || sc
.Match('/', '/')) {
213 sc
.SetState(SCE_POWERPRO_DEFAULT
);
217 case SCE_POWERPRO_COMMENTBLOCK
:
218 if (sc
.Match('*', '/')) {
220 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
224 case SCE_POWERPRO_COMMENTLINE
:
225 if (sc
.atLineStart
) {
226 sc
.SetState(SCE_POWERPRO_DEFAULT
);
230 case SCE_POWERPRO_DOUBLEQUOTEDSTRING
:
232 sc
.ChangeState(SCE_POWERPRO_STRINGEOL
);
233 } else if (sc
.ch
== '\\') {
234 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
237 } else if (sc
.ch
== '\"') {
238 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
242 case SCE_POWERPRO_SINGLEQUOTEDSTRING
:
244 sc
.ChangeState(SCE_POWERPRO_STRINGEOL
);
245 } else if (sc
.ch
== '\\') {
246 if (sc
.chNext
== '\"' || sc
.chNext
== '\'' || sc
.chNext
== '\\') {
249 } else if (sc
.ch
== '\'') {
250 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
254 case SCE_POWERPRO_STRINGEOL
:
255 if (sc
.atLineStart
) {
256 sc
.SetState(SCE_POWERPRO_DEFAULT
);
260 case SCE_POWERPRO_VERBATIM
:
262 if (sc
.chNext
== '\"') {
265 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
270 case SCE_POWERPRO_ALTQUOTE
:
272 if (sc
.chNext
== '#') {
275 sc
.ForwardSetState(SCE_POWERPRO_DEFAULT
);
280 case SCE_POWERPRO_FUNCTION
:
281 if (isspacechar(sc
.ch
) || sc
.ch
== '(') {
282 sc
.SetState(SCE_POWERPRO_DEFAULT
);
287 // Determine if a new state should be entered.
288 if (sc
.state
== SCE_POWERPRO_DEFAULT
) {
289 if (sc
.Match('?', '\"')) {
290 sc
.SetState(SCE_POWERPRO_VERBATIM
);
292 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
293 sc
.SetState(SCE_POWERPRO_NUMBER
);
294 }else if (sc
.Match('?','#')) {
295 if (sc
.ch
== '?' && sc
.chNext
== '#') {
296 sc
.SetState(SCE_POWERPRO_ALTQUOTE
);
299 } else if (IsFunction(styler
, sc
.currentPos
)) { //highlight <name> in 'function <name>'
300 sc
.SetState(SCE_POWERPRO_FUNCTION
);
301 } else if (onlySpaces
&& sc
.ch
== '@') { //alternate function definition [label]
302 sc
.SetState(SCE_POWERPRO_FUNCTION
);
303 } else if ((sc
.ch
> 0) && (setWordStart
.Contains(sc
.ch
) || (sc
.ch
== '?'))) {
304 sc
.SetState(SCE_POWERPRO_IDENTIFIER
);
305 } else if (sc
.Match(";;+")) {
306 sc
.SetState(SCE_POWERPRO_LINECONTINUE
);
307 } else if (sc
.Match('/', '*')) {
308 sc
.SetState(SCE_POWERPRO_COMMENTBLOCK
);
309 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
310 } else if (sc
.Match('/', '/')) {
311 sc
.SetState(SCE_POWERPRO_COMMENTLINE
);
312 } else if (onlySpaces
&& sc
.ch
== ';') { //legacy comment that can only have blank space in front of it
313 sc
.SetState(SCE_POWERPRO_COMMENTLINE
);
314 } else if (sc
.Match(";;")) {
315 sc
.SetState(SCE_POWERPRO_COMMENTLINE
);
316 } else if (sc
.ch
== '\"') {
317 sc
.SetState(SCE_POWERPRO_DOUBLEQUOTEDSTRING
);
318 } else if (sc
.ch
== '\'') {
319 sc
.SetState(SCE_POWERPRO_SINGLEQUOTEDSTRING
);
320 } else if (isoperator(static_cast<char>(sc
.ch
))) {
321 sc
.SetState(SCE_POWERPRO_OPERATOR
);
325 //maintain a record of whether or not all the preceding characters on
326 //a line are space characters
327 if (onlySpaces
&& !IsASpaceOrTab(sc
.ch
))
330 //reset when starting a new line
335 //*************************************
336 // Colourize the last word correctly
337 //*************************************
338 if (sc
.state
== SCE_POWERPRO_IDENTIFIER
)
340 if (keywords
.InList(s_save
)) {
341 sc
.ChangeState(SCE_POWERPRO_WORD
);
342 sc
.SetState(SCE_POWERPRO_DEFAULT
);
344 else if (keywords2
.InList(s_save
)) {
345 sc
.ChangeState(SCE_POWERPRO_WORD2
);
346 sc
.SetState(SCE_POWERPRO_DEFAULT
);
348 else if (keywords3
.InList(s_save
)) {
349 sc
.ChangeState(SCE_POWERPRO_WORD3
);
350 sc
.SetState(SCE_POWERPRO_DEFAULT
);
352 else if (keywords4
.InList(s_save
)) {
353 sc
.ChangeState(SCE_POWERPRO_WORD4
);
354 sc
.SetState(SCE_POWERPRO_DEFAULT
);
357 sc
.SetState(SCE_POWERPRO_DEFAULT
);
363 static void FoldPowerProDoc(unsigned int startPos
, int length
, int, WordList
*[], Accessor
&styler
)
365 //define the character sets
366 CharacterSet
setWordStart(CharacterSet::setAlpha
, "_@", 0x80, true);
367 CharacterSet
setWord(CharacterSet::setAlphaNum
, "._", 0x80, true);
369 //used to tell if we're recursively folding the whole document, or just a small piece (ie: if statement or 1 function)
370 bool isFoldingAll
= true;
372 int endPos
= startPos
+ length
;
373 int lastLine
= styler
.GetLine(styler
.Length()); //used to help fold the last line correctly
375 // get settings from the config files for folding comments and preprocessor lines
376 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
377 bool foldInComment
= styler
.GetPropertyInt("fold.comment") == 2;
378 bool foldCompact
= true;
380 // Backtrack to previous line in case need to fix its fold status
381 int lineCurrent
= styler
.GetLine(startPos
);
383 isFoldingAll
= false;
384 if (lineCurrent
> 0) {
386 startPos
= styler
.LineStart(lineCurrent
);
389 // vars for style of previous/current/next lines
390 int style
= GetStyleFirstWord(lineCurrent
,styler
);
393 // find the first previous line without continuation character at the end
394 while ((lineCurrent
> 0 && IsContinuationLine(lineCurrent
, styler
))
395 || (lineCurrent
> 1 && IsContinuationLine(lineCurrent
- 1, styler
))) {
397 startPos
= styler
.LineStart(lineCurrent
);
400 if (lineCurrent
> 0) {
401 stylePrev
= GetStyleFirstWord(lineCurrent
-1,styler
);
404 // vars for getting first word to check for keywords
405 bool isFirstWordStarted
= false;
406 bool isFirstWordEnded
= false;
408 const unsigned int FIRST_WORD_MAX_LEN
= 10;
409 char szFirstWord
[FIRST_WORD_MAX_LEN
] = "";
410 unsigned int firstWordLen
= 0;
414 bool isDoLastWord
= false;
416 // var for indentlevel
417 int levelCurrent
= SC_FOLDLEVELBASE
;
419 levelCurrent
= styler
.LevelAt(lineCurrent
-1) >> 16;
420 int levelNext
= levelCurrent
;
422 int visibleChars
= 0;
423 int functionCount
= 0;
425 char chNext
= styler
.SafeGetCharAt(startPos
);
427 char chPrevPrev
= '\0';
428 char chPrevPrevPrev
= '\0';
430 for (int i
= startPos
; i
< endPos
; i
++) {
433 chNext
= styler
.SafeGetCharAt(i
+ 1);
435 if ((ch
> 0) && setWord
.Contains(ch
))
438 // get the syle for the current character neede to check in comment
439 int stylech
= styler
.StyleAt(i
);
441 // start the capture of the first word
442 if (!isFirstWordStarted
&& (ch
> 0)) {
443 if (setWord
.Contains(ch
) || setWordStart
.Contains(ch
) || ch
== ';' || ch
== '/') {
444 isFirstWordStarted
= true;
445 if (firstWordLen
< FIRST_WORD_MAX_LEN
- 1) {
446 szFirstWord
[firstWordLen
++] = static_cast<char>(tolower(ch
));
447 szFirstWord
[firstWordLen
] = '\0';
450 } // continue capture of the first word on the line
451 else if (isFirstWordStarted
&& !isFirstWordEnded
&& (ch
> 0)) {
452 if (!setWord
.Contains(ch
)) {
453 isFirstWordEnded
= true;
455 else if (firstWordLen
< (FIRST_WORD_MAX_LEN
- 1)) {
456 szFirstWord
[firstWordLen
++] = static_cast<char>(tolower(ch
));
457 szFirstWord
[firstWordLen
] = '\0';
461 if (stylech
!= SCE_POWERPRO_COMMENTLINE
) {
463 //reset isDoLastWord if we find a character(ignoring spaces) after 'do'
464 if (isDoLastWord
&& (ch
> 0) && setWord
.Contains(ch
))
465 isDoLastWord
= false;
467 // --find out if the word "do" is the last on a "if" line--
468 // collect each letter and put it into a buffer 2 chars long
469 // if we end up with "do" in the buffer when we reach the end of
470 // the line, "do" was the last word on the line
471 if ((ch
> 0) && isFirstWordEnded
&& strcmp(szFirstWord
, "if") == 0) {
474 szDo
[1] = static_cast<char>(tolower(ch
));
477 if (strcmp(szDo
, "do") == 0)
480 } else if (szDolen
< 2) {
481 szDo
[szDolen
++] = static_cast<char>(tolower(ch
));
482 szDo
[szDolen
] = '\0';
487 // End of Line found so process the information
488 if ((ch
== '\r' && chNext
!= '\n') // \r\n
490 || i
== endPos
) { // end of selection
492 // **************************
493 // Folding logic for Keywords
494 // **************************
496 // if a keyword is found on the current line and the line doesn't end with ;;+ (continuation)
497 // and we are not inside a commentblock.
499 && chPrev
!= '+' && chPrevPrev
!= ';' && chPrevPrevPrev
!=';'
500 && (!IsStreamCommentStyle(style
) || foldInComment
) ) {
502 // only fold "if" last keyword is "then" (else its a one line if)
503 if (strcmp(szFirstWord
, "if") == 0 && isDoLastWord
)
506 // create new fold for these words
507 if (strcmp(szFirstWord
, "for") == 0)
510 //handle folding for functions/labels
511 //Note: Functions and labels don't have an explicit end like [end function]
512 // 1. functions/labels end at the start of another function
513 // 2. functions/labels end at the end of the file
514 if ((strcmp(szFirstWord
, "function") == 0) || (firstWordLen
> 0 && szFirstWord
[0] == '@')) {
515 if (isFoldingAll
) { //if we're folding the whole document (recursivly by lua script)
517 if (functionCount
> 0) {
524 } else { //if just folding a small piece (by clicking on the minus sign next to the word)
529 // end the fold for these words before the current line
530 if (strcmp(szFirstWord
, "endif") == 0 || strcmp(szFirstWord
, "endfor") == 0) {
535 // end the fold for these words before the current line and Start new fold
536 if (strcmp(szFirstWord
, "else") == 0 || strcmp(szFirstWord
, "elseif") == 0 )
540 // Preprocessor and Comment folding
541 int styleNext
= GetStyleFirstWord(lineCurrent
+ 1,styler
);
543 // *********************************
544 // Folding logic for Comment blocks
545 // *********************************
546 if (foldComment
&& IsStreamCommentStyle(style
)) {
548 // Start of a comment block
549 if (stylePrev
!= style
&& IsStreamCommentStyle(styleNext
) && styleNext
== style
) {
551 } // fold till the last line for normal comment lines
552 else if (IsStreamCommentStyle(stylePrev
)
553 && styleNext
!= SCE_POWERPRO_COMMENTLINE
554 && stylePrev
== SCE_POWERPRO_COMMENTLINE
555 && style
== SCE_POWERPRO_COMMENTLINE
) {
557 } // fold till the one but last line for Blockcomment lines
558 else if (IsStreamCommentStyle(stylePrev
)
559 && styleNext
!= SCE_POWERPRO_COMMENTBLOCK
560 && style
== SCE_POWERPRO_COMMENTBLOCK
) {
566 int levelUse
= levelCurrent
;
567 int lev
= levelUse
| levelNext
<< 16;
568 if (visibleChars
== 0 && foldCompact
)
569 lev
|= SC_FOLDLEVELWHITEFLAG
;
570 if (levelUse
< levelNext
)
571 lev
|= SC_FOLDLEVELHEADERFLAG
;
572 if (lev
!= styler
.LevelAt(lineCurrent
))
573 styler
.SetLevel(lineCurrent
, lev
);
575 // reset values for the next line
579 levelCurrent
= levelNext
;
582 // if the last characters are ;;+ then don't reset since the line continues on the next line.
583 if (chPrev
!= '+' && chPrevPrev
!= ';' && chPrevPrevPrev
!= ';') {
586 isFirstWordStarted
= false;
587 isFirstWordEnded
= false;
588 isDoLastWord
= false;
590 //blank out first word
591 for (unsigned int i
= 0; i
< FIRST_WORD_MAX_LEN
; i
++)
592 szFirstWord
[i
] = '\0';
596 // save the last processed characters
597 if ((ch
> 0) && !isspacechar(ch
)) {
598 chPrevPrevPrev
= chPrevPrev
;
604 //close folds on the last line - without this a 'phantom'
605 //fold can appear when an open fold is on the last line
606 //this can occur because functions and labels don't have an explicit end
607 if (lineCurrent
>= lastLine
) {
609 lev
|= SC_FOLDLEVELWHITEFLAG
;
610 styler
.SetLevel(lineCurrent
, lev
);
615 static const char * const powerProWordLists
[] = {
623 static void ColourisePowerProDocWrapper(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
625 ColourisePowerProDoc(startPos
, length
, initStyle
, keywordlists
, styler
, false);
628 LexerModule
lmPowerPro(SCLEX_POWERPRO
, ColourisePowerProDocWrapper
, "powerpro", FoldPowerProDoc
, powerProWordLists
);