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)
5 //
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
10 //
11 // Changes:
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
27 //			   - Cleanup
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.
30 
31 #include <string.h>
32 #include <assert.h>
33 #include <ctype.h>
34 
35 #include "ILexer.h"
36 #include "Scintilla.h"
37 #include "SciLexer.h"
38 
39 #include "WordList.h"
40 #include "LexAccessor.h"
41 #include "Accessor.h"
42 #include "StyleContext.h"
43 #include "CharacterSet.h"
44 #include "LexerModule.h"
45 
46 #ifdef SCI_NAMESPACE
47 using namespace Scintilla;
48 #endif
49 
IsStreamCommentStyle(int style)50 static inline bool IsStreamCommentStyle(int style) {
51 	return style == SCE_POWERPRO_COMMENTBLOCK;
52 }
53 
IsLineEndChar(unsigned char ch)54 static inline bool IsLineEndChar(unsigned char ch) {
55 	return 	ch == 0x0a 		//LF
56 			|| ch == 0x0c	//FF
57 			|| ch == 0x0d;	//CR
58 }
59 
IsContinuationLine(unsigned int szLine,Accessor & styler)60 static bool IsContinuationLine(unsigned int szLine, Accessor &styler)
61 {
62 	int startPos = styler.LineStart(szLine);
63 	int endPos = styler.LineStart(szLine + 1) - 2;
64 	while (startPos < endPos)
65 	{
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 == '+');
73 			}
74 		endPos--; // skip to next char
75 	}
76 	return false;
77 }
78 
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
GetStyleFirstWord(int szLine,Accessor & styler)81 static int GetStyleFirstWord(int szLine, Accessor &styler)
82 {
83 	int startPos = styler.LineStart(szLine);
84 	int endPos = styler.LineStart(szLine + 1) - 1;
85 	char ch = styler.SafeGetCharAt(startPos);
86 
87 	while (ch > 0 && isspacechar(ch) && startPos < endPos)
88 	{
89 		startPos++; // skip to next char
90 		ch = styler.SafeGetCharAt(startPos);
91 	}
92 	return styler.StyleAt(startPos);
93 }
94 
95 //returns true if there is a function to highlight
96 //used to highlight <name> in 'function <name>'
97 //note:
98 //		sample line (without quotes): "\tfunction asdf()
99 //		currentPos will be the position of 'a'
IsFunction(Accessor & styler,unsigned int currentPos)100 static bool IsFunction(Accessor &styler, unsigned int currentPos) {
101 
102 	const char function[10] = "function "; //10 includes \0
103 	unsigned int numberOfCharacters = sizeof(function) - 1;
104 	unsigned int position = currentPos - numberOfCharacters;
105 
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])
111 			return false;
112 	}
113 
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
120 			return true;
121 		if (c > 0 && IsLineEndChar(c))
122 			return true;
123 		else if (c > 0 && !IsASpaceOrTab(c))
124 			return false;
125 	}
126 
127 	//fall-through
128 	return false;
129 }
130 
ColourisePowerProDoc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler,bool caseSensitive)131 static void ColourisePowerProDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
132                             Accessor &styler, bool caseSensitive) {
133 
134 	WordList &keywords  = *keywordlists[0];
135 	WordList &keywords2 = *keywordlists[1];
136 	WordList &keywords3 = *keywordlists[2];
137 	WordList &keywords4 = *keywordlists[3];
138 
139 	//define the character sets
140 	CharacterSet setWordStart(CharacterSet::setAlpha, "_@", 0x80, true);
141 	CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
142 
143 	StyleContext sc(startPos, length, initStyle, styler);
144 	char s_save[100]; //for last line highlighting
145 
146 	//are there only spaces between the first letter of the line and the beginning of the line
147 	bool onlySpaces = true;
148 
149 	for (; sc.More(); sc.Forward()) {
150 
151 		// save the total current word for eof processing
152 		char s[100];
153 		sc.GetCurrentLowered(s, sizeof(s));
154 
155 		if ((sc.ch > 0) && setWord.Contains(sc.ch))
156 		{
157 			strcpy(s_save,s);
158 			int tp = static_cast<int>(strlen(s_save));
159 			if (tp < 99) {
160 				s_save[tp] = static_cast<char>(tolower(sc.ch));
161 				s_save[tp+1] = '\0';
162 			}
163 		}
164 
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);
170 			}
171 		}
172 
173 		// Determine if the current state should terminate.
174 		switch (sc.state) {
175 			case SCE_POWERPRO_OPERATOR:
176 				sc.SetState(SCE_POWERPRO_DEFAULT);
177 				break;
178 
179 			case SCE_POWERPRO_NUMBER:
180 
181 				if (!IsADigit(sc.ch))
182 					sc.SetState(SCE_POWERPRO_DEFAULT);
183 
184 				break;
185 
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
189 					char s[1000];
190 					if (caseSensitive) {
191 						sc.GetCurrent(s, sizeof(s));
192 					} else {
193 						sc.GetCurrentLowered(s, sizeof(s));
194 					}
195 
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);
204 					}
205 					sc.SetState(SCE_POWERPRO_DEFAULT);
206 				}
207 				break;
208 
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);
214 				}
215 				break;
216 
217 			case SCE_POWERPRO_COMMENTBLOCK:
218 				if (sc.Match('*', '/')) {
219 					sc.Forward();
220 					sc.ForwardSetState(SCE_POWERPRO_DEFAULT);
221 				}
222 				break;
223 
224 			case SCE_POWERPRO_COMMENTLINE:
225 				if (sc.atLineStart) {
226 					sc.SetState(SCE_POWERPRO_DEFAULT);
227 				}
228 				break;
229 
230 			case SCE_POWERPRO_DOUBLEQUOTEDSTRING:
231 				if (sc.atLineEnd) {
232 					sc.ChangeState(SCE_POWERPRO_STRINGEOL);
233 				} else if (sc.ch == '\\') {
234 					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
235 						sc.Forward();
236 					}
237 				} else if (sc.ch == '\"') {
238 					sc.ForwardSetState(SCE_POWERPRO_DEFAULT);
239 				}
240 				break;
241 
242 			case SCE_POWERPRO_SINGLEQUOTEDSTRING:
243 				if (sc.atLineEnd) {
244 					sc.ChangeState(SCE_POWERPRO_STRINGEOL);
245 				} else if (sc.ch == '\\') {
246 					if (sc.chNext == '\"' || sc.chNext == '\'' || sc.chNext == '\\') {
247 						sc.Forward();
248 					}
249 				} else if (sc.ch == '\'') {
250 					sc.ForwardSetState(SCE_POWERPRO_DEFAULT);
251 				}
252 				break;
253 
254 			case SCE_POWERPRO_STRINGEOL:
255 				if (sc.atLineStart) {
256 					sc.SetState(SCE_POWERPRO_DEFAULT);
257 				}
258 				break;
259 
260 			case SCE_POWERPRO_VERBATIM:
261 				if (sc.ch == '\"') {
262 					if (sc.chNext == '\"') {
263 						sc.Forward();
264 					} else {
265 						sc.ForwardSetState(SCE_POWERPRO_DEFAULT);
266 					}
267 				}
268 				break;
269 
270 			case SCE_POWERPRO_ALTQUOTE:
271 				if (sc.ch == '#') {
272 					if (sc.chNext == '#') {
273 						sc.Forward();
274 					} else {
275 						sc.ForwardSetState(SCE_POWERPRO_DEFAULT);
276 					}
277 				}
278 				break;
279 
280 			case SCE_POWERPRO_FUNCTION:
281 				if (isspacechar(sc.ch) || sc.ch == '(') {
282 					sc.SetState(SCE_POWERPRO_DEFAULT);
283 				}
284 			break;
285 		}
286 
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);
291 				sc.Forward();
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);
297 					sc.Forward();
298 				}
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);
322 			}
323 		}
324 
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))
328 			onlySpaces = false;
329 
330 		//reset when starting a new line
331 		if (sc.atLineEnd)
332 			onlySpaces = true;
333 	}
334 
335 	//*************************************
336 	// Colourize the last word correctly
337 	//*************************************
338 	if (sc.state == SCE_POWERPRO_IDENTIFIER)
339 	{
340 		if (keywords.InList(s_save)) {
341 			sc.ChangeState(SCE_POWERPRO_WORD);
342 			sc.SetState(SCE_POWERPRO_DEFAULT);
343 		}
344 		else if (keywords2.InList(s_save)) {
345 			sc.ChangeState(SCE_POWERPRO_WORD2);
346 			sc.SetState(SCE_POWERPRO_DEFAULT);
347 		}
348 		else if (keywords3.InList(s_save)) {
349 			sc.ChangeState(SCE_POWERPRO_WORD3);
350 			sc.SetState(SCE_POWERPRO_DEFAULT);
351 		}
352 		else if (keywords4.InList(s_save)) {
353 			sc.ChangeState(SCE_POWERPRO_WORD4);
354 			sc.SetState(SCE_POWERPRO_DEFAULT);
355 		}
356 		else {
357 			sc.SetState(SCE_POWERPRO_DEFAULT);
358 		}
359 	}
360 	sc.Complete();
361 }
362 
FoldPowerProDoc(unsigned int startPos,int length,int,WordList * [],Accessor & styler)363 static void FoldPowerProDoc(unsigned int startPos, int length, int, WordList *[], Accessor &styler)
364 {
365 	//define the character sets
366 	CharacterSet setWordStart(CharacterSet::setAlpha, "_@", 0x80, true);
367 	CharacterSet setWord(CharacterSet::setAlphaNum, "._", 0x80, true);
368 
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;
371 
372 	int endPos = startPos + length;
373 	int lastLine = styler.GetLine(styler.Length()); //used to help fold the last line correctly
374 
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;
379 
380 	// Backtrack to previous line in case need to fix its fold status
381 	int lineCurrent = styler.GetLine(startPos);
382 	if (startPos > 0) {
383 		isFoldingAll = false;
384 		if (lineCurrent > 0) {
385 			lineCurrent--;
386 			startPos = styler.LineStart(lineCurrent);
387 		}
388 	}
389 	// vars for style of previous/current/next lines
390 	int style = GetStyleFirstWord(lineCurrent,styler);
391 	int stylePrev = 0;
392 
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))) {
396 		lineCurrent--;
397 		startPos = styler.LineStart(lineCurrent);
398 	}
399 
400 	if (lineCurrent > 0) {
401 		stylePrev = GetStyleFirstWord(lineCurrent-1,styler);
402 	}
403 
404 	// vars for getting first word to check for keywords
405 	bool isFirstWordStarted = false;
406 	bool isFirstWordEnded = false;
407 
408 	const unsigned int FIRST_WORD_MAX_LEN = 10;
409 	char szFirstWord[FIRST_WORD_MAX_LEN] = "";
410 	unsigned int firstWordLen = 0;
411 
412 	char szDo[3]="";
413 	int	 szDolen = 0;
414 	bool isDoLastWord = false;
415 
416 	// var for indentlevel
417 	int levelCurrent = SC_FOLDLEVELBASE;
418 	if (lineCurrent > 0)
419 		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
420 	int levelNext = levelCurrent;
421 
422 	int	visibleChars = 0;
423 	int functionCount = 0;
424 
425 	char chNext = styler.SafeGetCharAt(startPos);
426 	char chPrev = '\0';
427 	char chPrevPrev = '\0';
428 	char chPrevPrevPrev = '\0';
429 
430 	for (int i = startPos; i < endPos; i++) {
431 
432 		char ch = chNext;
433 		chNext = styler.SafeGetCharAt(i + 1);
434 
435 		if ((ch > 0) && setWord.Contains(ch))
436 			visibleChars++;
437 
438 		// get the syle for the current character neede to check in comment
439 		int stylech = styler.StyleAt(i);
440 
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';
448 				}
449 			}
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;
454 			}
455 			else if (firstWordLen < (FIRST_WORD_MAX_LEN - 1)) {
456 				szFirstWord[firstWordLen++] = static_cast<char>(tolower(ch));
457 				szFirstWord[firstWordLen] = '\0';
458 			}
459 		}
460 
461 		if (stylech != SCE_POWERPRO_COMMENTLINE) {
462 
463 			//reset isDoLastWord if we find a character(ignoring spaces) after 'do'
464 			if (isDoLastWord && (ch > 0) && setWord.Contains(ch))
465 				isDoLastWord = false;
466 
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) {
472 				if (szDolen == 2) {
473 					szDo[0] = szDo[1];
474 					szDo[1] = static_cast<char>(tolower(ch));
475 					szDo[2] = '\0';
476 
477 					if (strcmp(szDo, "do") == 0)
478 						isDoLastWord = true;
479 
480 				} else if (szDolen < 2) {
481 					szDo[szDolen++] = static_cast<char>(tolower(ch));
482 					szDo[szDolen] = '\0';
483 				}
484 			}
485 		}
486 
487 		// End of Line found so process the information
488 		 if ((ch == '\r' && chNext != '\n') // \r\n
489 			|| ch == '\n' 					// \n
490 			|| i == endPos) {				// end of selection
491 
492 			// **************************
493 			// Folding logic for Keywords
494 			// **************************
495 
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.
498 			if (firstWordLen > 0
499 				&& chPrev != '+' && chPrevPrev != ';' && chPrevPrevPrev !=';'
500 				&& (!IsStreamCommentStyle(style) || foldInComment) ) {
501 
502 				// only fold "if" last keyword is "then"  (else its a one line if)
503 				if (strcmp(szFirstWord, "if") == 0  && isDoLastWord)
504 						levelNext++;
505 
506 				// create new fold for these words
507 				if (strcmp(szFirstWord, "for") == 0)
508 					levelNext++;
509 
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)
516 
517 						if (functionCount > 0) {
518 							levelCurrent--;
519 						} else {
520 							levelNext++;
521 						}
522 						functionCount++;
523 
524 					} else { //if just folding a small piece (by clicking on the minus sign next to the word)
525 						levelCurrent--;
526 					}
527 				}
528 
529 				// end the fold for these words before the current line
530 				if (strcmp(szFirstWord, "endif") == 0 || strcmp(szFirstWord, "endfor") == 0) {
531 						levelNext--;
532 						levelCurrent--;
533 				}
534 
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 )
537 						levelCurrent--;
538 
539 			}
540 			// Preprocessor and Comment folding
541 			int styleNext = GetStyleFirstWord(lineCurrent + 1,styler);
542 
543 			// *********************************
544 			// Folding logic for Comment blocks
545 			// *********************************
546 			if (foldComment && IsStreamCommentStyle(style)) {
547 
548 				// Start of a comment block
549 				if (stylePrev != style && IsStreamCommentStyle(styleNext) && styleNext == style) {
550 				    levelNext++;
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) {
556 					levelNext--;
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) {
561 					levelNext--;
562 					levelCurrent--;
563 				}
564 			}
565 
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);
574 
575 			// reset values for the next line
576 			lineCurrent++;
577 			stylePrev = style;
578 			style = styleNext;
579 			levelCurrent = levelNext;
580 			visibleChars = 0;
581 
582 			// if the last characters are ;;+ then don't reset since the line continues on the next line.
583 			if (chPrev != '+' && chPrevPrev != ';' && chPrevPrevPrev != ';') {
584 				firstWordLen = 0;
585 				szDolen = 0;
586 				isFirstWordStarted = false;
587 				isFirstWordEnded = false;
588 				isDoLastWord = false;
589 
590 				//blank out first word
591 				for (unsigned int i = 0; i < FIRST_WORD_MAX_LEN; i++)
592 					szFirstWord[i] = '\0';
593 			}
594 		}
595 
596 		// save the last processed characters
597 		if ((ch > 0) && !isspacechar(ch)) {
598 			chPrevPrevPrev = chPrevPrev;
599 			chPrevPrev = chPrev;
600 			chPrev = ch;
601 		}
602 	}
603 
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) {
608 		int lev = 0;
609 		lev |= SC_FOLDLEVELWHITEFLAG;
610 		styler.SetLevel(lineCurrent, lev);
611 	}
612 
613 }
614 
615 static const char * const powerProWordLists[] = {
616             "Keyword list 1",
617             "Keyword list 2",
618             "Keyword list 3",
619             "Keyword list 4",
620             0,
621         };
622 
ColourisePowerProDocWrapper(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)623 static void ColourisePowerProDocWrapper(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
624                                        Accessor &styler) {
625 	ColourisePowerProDoc(startPos, length, initStyle, keywordlists, styler, false);
626 }
627 
628 LexerModule lmPowerPro(SCLEX_POWERPRO, ColourisePowerProDocWrapper, "powerpro", FoldPowerProDoc, powerProWordLists);
629 
630 
631