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);