1 // Scintilla source code edit control
2 // @file LexAU3.cxx
3 // Lexer for AutoIt3  http://www.hiddensoft.com/autoit3
4 // by Jos van der Zande, jvdzande@yahoo.com
5 //
6 // Changes:
7 // March 28, 2004 - Added the standard Folding code
8 // April 21, 2004 - Added Preprosessor Table + Syntax Highlighting
9 //                  Fixed Number highlighting
10 //                  Changed default isoperator to IsAOperator to have a better match to AutoIt3
11 //                  Fixed "#comments_start" -> "#comments-start"
12 //                  Fixed "#comments_end" -> "#comments-end"
13 //                  Fixed Sendkeys in Strings when not terminated with }
14 //                  Added support for Sendkey strings that have second parameter e.g. {UP 5} or {a down}
15 // April 26, 2004 - Fixed # pre-processor statement inside of comment block would invalidly change the color.
16 //                  Added logic for #include <xyz.au3> to treat the <> as string
17 //                  Added underscore to IsAOperator.
18 // May 17, 2004   - Changed the folding logic from indent to keyword folding.
19 //                  Added Folding logic for blocks of single-commentlines or commentblock.
20 //                        triggered by: fold.comment=1
21 //                  Added Folding logic for preprocessor blocks triggered by fold.preprocessor=1
22 //                  Added Special for #region - #endregion syntax highlight and folding.
23 // May 30, 2004   - Fixed issue with continuation lines on If statements.
24 // June 5, 2004   - Added comma to Operators for better readability.
25 //                  Added fold.compact support set with fold.compact=1
26 //                  Changed folding inside of #cs-#ce. Default is no keyword folding inside comment blocks when fold.comment=1
27 //                        it will now only happen when fold.comment=2.
28 // Sep 5, 2004    - Added logic to handle colourizing words on the last line.
29 //                        Typed Characters now show as "default" till they match any table.
30 // Oct 10, 2004   - Added logic to show Comments in "Special" directives.
31 // Nov  1, 2004   - Added better testing for Numbers supporting x and e notation.
32 // Nov 28, 2004   - Added logic to handle continuation lines for syntax highlighting.
33 // Jan 10, 2005   - Added Abbreviations Keyword used for expansion
34 // Mar 24, 2005   - Updated Abbreviations Keywords to fix when followed by Operator.
35 // Apr 18, 2005   - Updated #CE/#Comment-End logic to take a linecomment ";" into account
36 //                - Added folding support for With...EndWith
37 //                - Added support for a DOT in variable names
38 //                - Fixed Underscore in CommentBlock
39 // May 23, 2005   - Fixed the SentKey lexing in case of a missing }
40 // Aug 11, 2005   - Fixed possible bug with s_save length > 100.
41 // Aug 23, 2005   - Added Switch/endswitch support to the folding logic.
42 // Sep 27, 2005   - Fixed the SentKey lexing logic in case of multiple sentkeys.
43 // Mar 12, 2006   - Fixed issue with <> coloring as String in stead of Operator in rare occasions.
44 // Apr  8, 2006   - Added support for AutoIt3 Standard UDF library (SCE_AU3_UDF)
45 // Mar  9, 2007   - Fixed bug with + following a String getting the wrong Color.
46 // Jun 20, 2007   - Fixed Commentblock issue when LF's are used as EOL.
47 // Jul 26, 2007   - Fixed #endregion undetected bug.
48 //
49 // Copyright for Scintilla: 1998-2001 by Neil Hodgson <neilh@scintilla.org>
50 // The License.txt file describes the conditions under which this software may be distributed.
51 // Scintilla source code edit control
52 
53 #include <stdlib.h>
54 #include <string.h>
55 #include <stdio.h>
56 #include <stdarg.h>
57 #include <assert.h>
58 #include <ctype.h>
59 
60 #include "ILexer.h"
61 #include "Scintilla.h"
62 #include "SciLexer.h"
63 
64 #include "WordList.h"
65 #include "LexAccessor.h"
66 #include "Accessor.h"
67 #include "StyleContext.h"
68 #include "CharacterSet.h"
69 #include "LexerModule.h"
70 
71 #ifdef SCI_NAMESPACE
72 using namespace Scintilla;
73 #endif
74 
IsTypeCharacter(const int ch)75 static inline bool IsTypeCharacter(const int ch)
76 {
77     return ch == '$';
78 }
IsAWordChar(const int ch)79 static inline bool IsAWordChar(const int ch)
80 {
81     return (ch < 0x80) && (isalnum(ch) || ch == '_');
82 }
83 
IsAWordStart(const int ch)84 static inline bool IsAWordStart(const int ch)
85 {
86     return (ch < 0x80) && (isalnum(ch) || ch == '_' || ch == '@' || ch == '#' || ch == '$' || ch == '.');
87 }
88 
IsAOperator(char ch)89 static inline bool IsAOperator(char ch) {
90 	if (IsASCII(ch) && isalnum(ch))
91 		return false;
92 	if (ch == '+' || ch == '-' || ch == '*' || ch == '/' ||
93 	    ch == '&' || ch == '^' || ch == '=' || ch == '<' || ch == '>' ||
94 	    ch == '(' || ch == ')' || ch == '[' || ch == ']' || ch == ',' )
95 		return true;
96 	return false;
97 }
98 
99 ///////////////////////////////////////////////////////////////////////////////
100 // GetSendKey() filters the portion before and after a/multiple space(s)
101 // and return the first portion to be looked-up in the table
102 // also check if the second portion is valid... (up,down.on.off,toggle or a number)
103 ///////////////////////////////////////////////////////////////////////////////
104 
GetSendKey(const char * szLine,char * szKey)105 static int GetSendKey(const char *szLine, char *szKey)
106 {
107 	int		nFlag	= 0;
108 	int		nStartFound	= 0;
109 	int		nKeyPos	= 0;
110 	int		nSpecPos= 0;
111 	int		nSpecNum= 1;
112 	int		nPos	= 0;
113 	char	cTemp;
114 	char	szSpecial[100];
115 
116 	// split the portion of the sendkey in the part before and after the spaces
117 	while ( ( (cTemp = szLine[nPos]) != '\0'))
118 	{
119 		// skip leading Ctrl/Shift/Alt state
120 		if (cTemp == '{') {
121 			nStartFound = 1;
122 		}
123 		//
124 		if (nStartFound == 1) {
125 			if ((cTemp == ' ') && (nFlag == 0) ) // get the stuff till first space
126 			{
127 				nFlag = 1;
128 				// Add } to the end of the first bit for table lookup later.
129 				szKey[nKeyPos++] = '}';
130 			}
131 			else if (cTemp == ' ')
132 			{
133 				// skip other spaces
134 			}
135 			else if (nFlag == 0)
136 			{
137 				// save first portion into var till space or } is hit
138 				szKey[nKeyPos++] = cTemp;
139 			}
140 			else if ((nFlag == 1) && (cTemp != '}'))
141 			{
142 				// Save second portion into var...
143 				szSpecial[nSpecPos++] = cTemp;
144 				// check if Second portion is all numbers for repeat fuction
145 				if (isdigit(cTemp) == false) {nSpecNum = 0;}
146 			}
147 		}
148 		nPos++;									// skip to next char
149 
150 	} // End While
151 
152 
153 	// Check if the second portion is either a number or one of these keywords
154 	szKey[nKeyPos] = '\0';
155 	szSpecial[nSpecPos] = '\0';
156 	if (strcmp(szSpecial,"down")== 0    || strcmp(szSpecial,"up")== 0  ||
157 		strcmp(szSpecial,"on")== 0      || strcmp(szSpecial,"off")== 0 ||
158 		strcmp(szSpecial,"toggle")== 0  || nSpecNum == 1 )
159 	{
160 		nFlag = 0;
161 	}
162 	else
163 	{
164 		nFlag = 1;
165 	}
166 	return nFlag;  // 1 is bad, 0 is good
167 
168 } // GetSendKey()
169 
170 //
171 // Routine to check the last "none comment" character on a line to see if its a continuation
172 //
IsContinuationLine(unsigned int szLine,Accessor & styler)173 static bool IsContinuationLine(unsigned int szLine, Accessor &styler)
174 {
175 	int nsPos = styler.LineStart(szLine);
176 	int nePos = styler.LineStart(szLine+1) - 2;
177 	//int stylech = styler.StyleAt(nsPos);
178 	while (nsPos < nePos)
179 	{
180 		//stylech = styler.StyleAt(nePos);
181 		int stylech = styler.StyleAt(nsPos);
182 		if (!(stylech == SCE_AU3_COMMENT)) {
183 			char ch = styler.SafeGetCharAt(nePos);
184 			if (!isspacechar(ch)) {
185 				if (ch == '_')
186 					return true;
187 				else
188 					return false;
189 			}
190 		}
191 		nePos--; // skip to next char
192 	} // End While
193 	return false;
194 } // IsContinuationLine()
195 
196 //
197 // syntax highlighting logic
ColouriseAU3Doc(unsigned int startPos,int length,int initStyle,WordList * keywordlists[],Accessor & styler)198 static void ColouriseAU3Doc(unsigned int startPos,
199 							int length, int initStyle,
200 							WordList *keywordlists[],
201 							Accessor &styler) {
202 
203     WordList &keywords = *keywordlists[0];
204     WordList &keywords2 = *keywordlists[1];
205     WordList &keywords3 = *keywordlists[2];
206     WordList &keywords4 = *keywordlists[3];
207     WordList &keywords5 = *keywordlists[4];
208     WordList &keywords6 = *keywordlists[5];
209     WordList &keywords7 = *keywordlists[6];
210     WordList &keywords8 = *keywordlists[7];
211 	// find the first previous line without continuation character at the end
212 	int lineCurrent = styler.GetLine(startPos);
213 	int s_startPos = startPos;
214 	// When not inside a Block comment: find First line without _
215 	if (!(initStyle==SCE_AU3_COMMENTBLOCK)) {
216 		while ((lineCurrent > 0 && IsContinuationLine(lineCurrent,styler)) ||
217 			   (lineCurrent > 1 && IsContinuationLine(lineCurrent-1,styler))) {
218 			lineCurrent--;
219 			startPos = styler.LineStart(lineCurrent); // get start position
220 			initStyle =  0;                           // reset the start style to 0
221 		}
222 	}
223 	// Set the new length to include it from the start and set the start position
224 	length = length + s_startPos - startPos;      // correct the total length to process
225     styler.StartAt(startPos);
226 
227     StyleContext sc(startPos, length, initStyle, styler);
228 	char si;     // string indicator "=1 '=2
229 	char ni;     // Numeric indicator error=9 normal=0 normal+dec=1 hex=2 Enot=3
230 	char ci;     // comment indicator 0=not linecomment(;)
231 	char s_save[100] = "";
232 	si=0;
233 	ni=0;
234 	ci=0;
235 	//$$$
236     for (; sc.More(); sc.Forward()) {
237 		char s[100];
238 		sc.GetCurrentLowered(s, sizeof(s));
239 		// **********************************************
240 		// save the total current word for eof processing
241 		if (IsAWordChar(sc.ch) || sc.ch == '}')
242 		{
243 			strcpy(s_save,s);
244 			int tp = static_cast<int>(strlen(s_save));
245 			if (tp < 99) {
246 				s_save[tp] = static_cast<char>(tolower(sc.ch));
247 				s_save[tp+1] = '\0';
248 			}
249 		}
250 		// **********************************************
251 		//
252 		switch (sc.state)
253         {
254             case SCE_AU3_COMMENTBLOCK:
255             {
256 				//Reset at line end
257 				if (sc.atLineEnd) {
258 					ci=0;
259 					if (strcmp(s, "#ce")== 0 || strcmp(s, "#comments-end")== 0) {
260 						if (sc.atLineEnd)
261 							sc.SetState(SCE_AU3_DEFAULT);
262 						else
263 							sc.SetState(SCE_AU3_COMMENTBLOCK);
264 					}
265 					break;
266 				}
267 				//skip rest of line when a ; is encountered
268 				if (sc.chPrev == ';') {
269 					ci=2;
270 					sc.SetState(SCE_AU3_COMMENTBLOCK);
271 				}
272 				// skip rest of the line
273 				if (ci==2)
274 					break;
275 				// check when first character is detected on the line
276 				if (ci==0) {
277 					if (IsAWordStart(static_cast<char>(sc.ch)) || IsAOperator(static_cast<char>(sc.ch))) {
278 						ci=1;
279 						sc.SetState(SCE_AU3_COMMENTBLOCK);
280 					}
281 					break;
282 				}
283 				if (!(IsAWordChar(sc.ch) || (sc.ch == '-' && strcmp(s, "#comments") == 0))) {
284 					if ((strcmp(s, "#ce")== 0 || strcmp(s, "#comments-end")== 0))
285 							sc.SetState(SCE_AU3_COMMENT);  // set to comment line for the rest of the line
286 					else
287 						ci=2;  // line doesn't begin with #CE so skip the rest of the line
288 				}
289 				break;
290 			}
291             case SCE_AU3_COMMENT:
292             {
293                 if (sc.atLineEnd) {sc.SetState(SCE_AU3_DEFAULT);}
294                 break;
295             }
296             case SCE_AU3_OPERATOR:
297             {
298                 // check if its a COMobject
299 				if (sc.chPrev == '.' && IsAWordChar(sc.ch)) {
300 					sc.SetState(SCE_AU3_COMOBJ);
301 				}
302 				else {
303 					sc.SetState(SCE_AU3_DEFAULT);
304 				}
305                 break;
306             }
307             case SCE_AU3_SPECIAL:
308             {
309                 if (sc.ch == ';') {sc.SetState(SCE_AU3_COMMENT);}
310 				if (sc.atLineEnd) {sc.SetState(SCE_AU3_DEFAULT);}
311                 break;
312             }
313             case SCE_AU3_KEYWORD:
314             {
315                 if (!(IsAWordChar(sc.ch) || (sc.ch == '-' && (strcmp(s, "#comments") == 0 || strcmp(s, "#include") == 0))))
316                 {
317                     if (!IsTypeCharacter(sc.ch))
318                     {
319 						if (strcmp(s, "#cs")== 0 || strcmp(s, "#comments-start")== 0 )
320 						{
321 							sc.ChangeState(SCE_AU3_COMMENTBLOCK);
322 							sc.SetState(SCE_AU3_COMMENTBLOCK);
323 							break;
324 						}
325 						else if (keywords.InList(s)) {
326 							sc.ChangeState(SCE_AU3_KEYWORD);
327 							sc.SetState(SCE_AU3_DEFAULT);
328 						}
329 						else if (keywords2.InList(s)) {
330 							sc.ChangeState(SCE_AU3_FUNCTION);
331 							sc.SetState(SCE_AU3_DEFAULT);
332 						}
333 						else if (keywords3.InList(s)) {
334 							sc.ChangeState(SCE_AU3_MACRO);
335 							sc.SetState(SCE_AU3_DEFAULT);
336 						}
337 						else if (keywords5.InList(s)) {
338 							sc.ChangeState(SCE_AU3_PREPROCESSOR);
339 							sc.SetState(SCE_AU3_DEFAULT);
340 							if (strcmp(s, "#include")== 0)
341 							{
342 								si = 3;   // use to determine string start for #inlude <>
343 							}
344 						}
345 						else if (keywords6.InList(s)) {
346 							sc.ChangeState(SCE_AU3_SPECIAL);
347 							sc.SetState(SCE_AU3_SPECIAL);
348 						}
349 						else if ((keywords7.InList(s)) && (!IsAOperator(static_cast<char>(sc.ch)))) {
350 							sc.ChangeState(SCE_AU3_EXPAND);
351 							sc.SetState(SCE_AU3_DEFAULT);
352 						}
353 						else if (keywords8.InList(s)) {
354 							sc.ChangeState(SCE_AU3_UDF);
355 							sc.SetState(SCE_AU3_DEFAULT);
356 						}
357 						else if (strcmp(s, "_") == 0) {
358 							sc.ChangeState(SCE_AU3_OPERATOR);
359 							sc.SetState(SCE_AU3_DEFAULT);
360 						}
361 						else if (!IsAWordChar(sc.ch)) {
362 							sc.ChangeState(SCE_AU3_DEFAULT);
363 							sc.SetState(SCE_AU3_DEFAULT);
364 						}
365 					}
366 				}
367                 if (sc.atLineEnd) {
368 					sc.SetState(SCE_AU3_DEFAULT);}
369                 break;
370             }
371 			case SCE_AU3_NUMBER:
372             {
373 				// Numeric indicator error=9 normal=0 normal+dec=1 hex=2 E-not=3
374 				//
375 				// test for Hex notation
376 				if (strcmp(s, "0") == 0 && (sc.ch == 'x' || sc.ch == 'X') && ni == 0)
377 				{
378 					ni = 2;
379 					break;
380 				}
381 				// test for E notation
382 				if (IsADigit(sc.chPrev) && (sc.ch == 'e' || sc.ch == 'E') && ni <= 1)
383 				{
384 					ni = 3;
385 					break;
386 				}
387 				//  Allow Hex characters inside hex numeric strings
388 				if ((ni == 2) &&
389 					(sc.ch == 'a' || sc.ch == 'b' || sc.ch == 'c' || sc.ch == 'd' || sc.ch == 'e' || sc.ch == 'f' ||
390 					 sc.ch == 'A' || sc.ch == 'B' || sc.ch == 'C' || sc.ch == 'D' || sc.ch == 'E' || sc.ch == 'F' ))
391 				{
392 					break;
393 				}
394 				// test for 1 dec point only
395 				if (sc.ch == '.')
396 				{
397 					if (ni==0)
398 					{
399 						ni=1;
400 					}
401 					else
402 					{
403 						ni=9;
404 					}
405 					break;
406 				}
407 				// end of numeric string ?
408 				if (!(IsADigit(sc.ch)))
409 				{
410 					if (ni==9)
411 					{
412 						sc.ChangeState(SCE_AU3_DEFAULT);
413 					}
414 					sc.SetState(SCE_AU3_DEFAULT);
415 				}
416 				break;
417 			}
418 			case SCE_AU3_VARIABLE:
419 			{
420 				// Check if its a COMObject
421 				if (sc.ch == '.' && !IsADigit(sc.chNext)) {
422 					sc.SetState(SCE_AU3_OPERATOR);
423 				}
424 				else if (!IsAWordChar(sc.ch)) {
425 					sc.SetState(SCE_AU3_DEFAULT);
426 				}
427 				break;
428             }
429 			case SCE_AU3_COMOBJ:
430 			{
431 				if (!(IsAWordChar(sc.ch))) {
432 					sc.SetState(SCE_AU3_DEFAULT);
433 				}
434 				break;
435             }
436             case SCE_AU3_STRING:
437             {
438 				// check for " to end a double qouted string or
439 				// check for ' to end a single qouted string
440 	            if ((si == 1 && sc.ch == '\"') || (si == 2 && sc.ch == '\'') || (si == 3 && sc.ch == '>'))
441 				{
442 					sc.ForwardSetState(SCE_AU3_DEFAULT);
443 					si=0;
444 					break;
445 				}
446                 if (sc.atLineEnd)
447 				{
448 					si=0;
449 					// at line end and not found a continuation char then reset to default
450 					int lineCurrent = styler.GetLine(sc.currentPos);
451 					if (!IsContinuationLine(lineCurrent,styler))
452 					{
453 						sc.SetState(SCE_AU3_DEFAULT);
454 						break;
455 					}
456 				}
457 				// find Sendkeys in a STRING
458 				if (sc.ch == '{' || sc.ch == '+' || sc.ch == '!' || sc.ch == '^' || sc.ch == '#' ) {
459 					sc.SetState(SCE_AU3_SENT);}
460 				break;
461             }
462 
463             case SCE_AU3_SENT:
464             {
465 				// Send key string ended
466 				if (sc.chPrev == '}' && sc.ch != '}')
467 				{
468 					// set color to SENDKEY when valid sendkey .. else set back to regular string
469 					char sk[100];
470 					// split {111 222} and return {111} and check if 222 is valid.
471 					// if return code = 1 then invalid 222 so must be string
472 					if (GetSendKey(s,sk))
473 					{
474 						sc.ChangeState(SCE_AU3_STRING);
475 					}
476 					// if single char between {?} then its ok as sendkey for a single character
477 					else if (strlen(sk) == 3)
478 					{
479 						sc.ChangeState(SCE_AU3_SENT);
480 					}
481 					// if sendkey {111} is in table then ok as sendkey
482 					else if (keywords4.InList(sk))
483 					{
484 						sc.ChangeState(SCE_AU3_SENT);
485 					}
486 					else
487 					{
488 						sc.ChangeState(SCE_AU3_STRING);
489 					}
490 					sc.SetState(SCE_AU3_STRING);
491 				}
492 				else
493 				{
494 					// check if the start is a valid SendKey start
495 					int		nPos	= 0;
496 					int		nState	= 1;
497 					char	cTemp;
498 					while (!(nState == 2) && ((cTemp = s[nPos]) != '\0'))
499 					{
500 						if (cTemp == '{' && nState == 1)
501 						{
502 							nState = 2;
503 						}
504 						if (nState == 1 && !(cTemp == '+' || cTemp == '!' || cTemp == '^' || cTemp == '#' ))
505 						{
506 							nState = 0;
507 						}
508 						nPos++;
509 					}
510 					//Verify characters infront of { ... if not assume  regular string
511 					if (nState == 1 && (!(sc.ch == '{' || sc.ch == '+' || sc.ch == '!' || sc.ch == '^' || sc.ch == '#' ))) {
512 						sc.ChangeState(SCE_AU3_STRING);
513 						sc.SetState(SCE_AU3_STRING);
514 					}
515 					// If invalid character found then assume its a regular string
516 					if (nState == 0) {
517 						sc.ChangeState(SCE_AU3_STRING);
518 						sc.SetState(SCE_AU3_STRING);
519 					}
520 				}
521 				// check if next portion is again a sendkey
522 				if (sc.atLineEnd)
523 				{
524 					sc.ChangeState(SCE_AU3_STRING);
525 					sc.SetState(SCE_AU3_DEFAULT);
526 					si = 0;  // reset string indicator
527 				}
528 				//* check in next characters following a sentkey are again a sent key
529 				// Need this test incase of 2 sentkeys like {F1}{ENTER} but not detect {{}
530 				if (sc.state == SCE_AU3_STRING && (sc.ch == '{' || sc.ch == '+' || sc.ch == '!' || sc.ch == '^' || sc.ch == '#' )) {
531 					sc.SetState(SCE_AU3_SENT);}
532 				// check to see if the string ended...
533 				// Sendkey string isn't complete but the string ended....
534 				if ((si == 1 && sc.ch == '\"') || (si == 2 && sc.ch == '\''))
535 				{
536 					sc.ChangeState(SCE_AU3_STRING);
537 					sc.ForwardSetState(SCE_AU3_DEFAULT);
538 				}
539 				break;
540             }
541         }  //switch (sc.state)
542 
543         // Determine if a new state should be entered:
544 
545 		if (sc.state == SCE_AU3_DEFAULT)
546         {
547             if (sc.ch == ';') {sc.SetState(SCE_AU3_COMMENT);}
548             else if (sc.ch == '#') {sc.SetState(SCE_AU3_KEYWORD);}
549             else if (sc.ch == '$') {sc.SetState(SCE_AU3_VARIABLE);}
550             else if (sc.ch == '.' && !IsADigit(sc.chNext)) {sc.SetState(SCE_AU3_OPERATOR);}
551             else if (sc.ch == '@') {sc.SetState(SCE_AU3_KEYWORD);}
552             //else if (sc.ch == '_') {sc.SetState(SCE_AU3_KEYWORD);}
553             else if (sc.ch == '<' && si==3) {sc.SetState(SCE_AU3_STRING);}  // string after #include
554             else if (sc.ch == '\"') {
555 				sc.SetState(SCE_AU3_STRING);
556 				si = 1;	}
557             else if (sc.ch == '\'') {
558 				sc.SetState(SCE_AU3_STRING);
559 				si = 2;	}
560             else if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext)))
561 			{
562 				sc.SetState(SCE_AU3_NUMBER);
563 				ni = 0;
564 			}
565             else if (IsAWordStart(sc.ch)) {sc.SetState(SCE_AU3_KEYWORD);}
566             else if (IsAOperator(static_cast<char>(sc.ch))) {sc.SetState(SCE_AU3_OPERATOR);}
567 			else if (sc.atLineEnd) {sc.SetState(SCE_AU3_DEFAULT);}
568         }
569     }      //for (; sc.More(); sc.Forward())
570 
571 	//*************************************
572 	// Colourize the last word correctly
573 	//*************************************
574 	if (sc.state == SCE_AU3_KEYWORD)
575 		{
576 		if (strcmp(s_save, "#cs")== 0 || strcmp(s_save, "#comments-start")== 0 )
577 		{
578 			sc.ChangeState(SCE_AU3_COMMENTBLOCK);
579 			sc.SetState(SCE_AU3_COMMENTBLOCK);
580 		}
581 		else if (keywords.InList(s_save)) {
582 			sc.ChangeState(SCE_AU3_KEYWORD);
583 			sc.SetState(SCE_AU3_KEYWORD);
584 		}
585 		else if (keywords2.InList(s_save)) {
586 			sc.ChangeState(SCE_AU3_FUNCTION);
587 			sc.SetState(SCE_AU3_FUNCTION);
588 		}
589 		else if (keywords3.InList(s_save)) {
590 			sc.ChangeState(SCE_AU3_MACRO);
591 			sc.SetState(SCE_AU3_MACRO);
592 		}
593 		else if (keywords5.InList(s_save)) {
594 			sc.ChangeState(SCE_AU3_PREPROCESSOR);
595 			sc.SetState(SCE_AU3_PREPROCESSOR);
596 		}
597 		else if (keywords6.InList(s_save)) {
598 			sc.ChangeState(SCE_AU3_SPECIAL);
599 			sc.SetState(SCE_AU3_SPECIAL);
600 		}
601 		else if (keywords7.InList(s_save) && sc.atLineEnd) {
602 			sc.ChangeState(SCE_AU3_EXPAND);
603 			sc.SetState(SCE_AU3_EXPAND);
604 		}
605 		else if (keywords8.InList(s_save)) {
606 			sc.ChangeState(SCE_AU3_UDF);
607 			sc.SetState(SCE_AU3_UDF);
608 		}
609 		else {
610 			sc.ChangeState(SCE_AU3_DEFAULT);
611 			sc.SetState(SCE_AU3_DEFAULT);
612 		}
613 	}
614 	if (sc.state == SCE_AU3_SENT)
615     {
616 		// Send key string ended
617 		if (sc.chPrev == '}' && sc.ch != '}')
618 		{
619 			// set color to SENDKEY when valid sendkey .. else set back to regular string
620 			char sk[100];
621 			// split {111 222} and return {111} and check if 222 is valid.
622 			// if return code = 1 then invalid 222 so must be string
623 			if (GetSendKey(s_save,sk))
624 			{
625 				sc.ChangeState(SCE_AU3_STRING);
626 			}
627 			// if single char between {?} then its ok as sendkey for a single character
628 			else if (strlen(sk) == 3)
629 			{
630 				sc.ChangeState(SCE_AU3_SENT);
631 			}
632 			// if sendkey {111} is in table then ok as sendkey
633 			else if (keywords4.InList(sk))
634 			{
635 				sc.ChangeState(SCE_AU3_SENT);
636 			}
637 			else
638 			{
639 				sc.ChangeState(SCE_AU3_STRING);
640 			}
641 			sc.SetState(SCE_AU3_STRING);
642 		}
643 		// check if next portion is again a sendkey
644 		if (sc.atLineEnd)
645 		{
646 			sc.ChangeState(SCE_AU3_STRING);
647 			sc.SetState(SCE_AU3_DEFAULT);
648 		}
649     }
650 	//*************************************
651 	sc.Complete();
652 }
653 
654 //
IsStreamCommentStyle(int style)655 static bool IsStreamCommentStyle(int style) {
656 	return style == SCE_AU3_COMMENT || style == SCE_AU3_COMMENTBLOCK;
657 }
658 
659 //
660 // Routine to find first none space on the current line and return its Style
661 // needed for comment lines not starting on pos 1
GetStyleFirstWord(unsigned int szLine,Accessor & styler)662 static int GetStyleFirstWord(unsigned int szLine, Accessor &styler)
663 {
664 	int nsPos = styler.LineStart(szLine);
665 	int nePos = styler.LineStart(szLine+1) - 1;
666 	while (isspacechar(styler.SafeGetCharAt(nsPos)) && nsPos < nePos)
667 	{
668 		nsPos++; // skip to next char
669 
670 	} // End While
671 	return styler.StyleAt(nsPos);
672 
673 } // GetStyleFirstWord()
674 
675 
676 //
FoldAU3Doc(unsigned int startPos,int length,int,WordList * [],Accessor & styler)677 static void FoldAU3Doc(unsigned int startPos, int length, int, WordList *[], Accessor &styler)
678 {
679 	int endPos = startPos + length;
680 	// get settings from the config files for folding comments and preprocessor lines
681 	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
682 	bool foldInComment = styler.GetPropertyInt("fold.comment") == 2;
683 	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
684 	bool foldpreprocessor = styler.GetPropertyInt("fold.preprocessor") != 0;
685 	// Backtrack to previous line in case need to fix its fold status
686 	int lineCurrent = styler.GetLine(startPos);
687 	if (startPos > 0) {
688 		if (lineCurrent > 0) {
689 			lineCurrent--;
690 			startPos = styler.LineStart(lineCurrent);
691 		}
692 	}
693 	// vars for style of previous/current/next lines
694 	int style = GetStyleFirstWord(lineCurrent,styler);
695 	int stylePrev = 0;
696 	// find the first previous line without continuation character at the end
697 	while ((lineCurrent > 0 && IsContinuationLine(lineCurrent,styler)) ||
698 	       (lineCurrent > 1 && IsContinuationLine(lineCurrent-1,styler))) {
699 		lineCurrent--;
700 		startPos = styler.LineStart(lineCurrent);
701 	}
702 	if (lineCurrent > 0) {
703 		stylePrev = GetStyleFirstWord(lineCurrent-1,styler);
704 	}
705 	// vars for getting first word to check for keywords
706 	bool FirstWordStart = false;
707 	bool FirstWordEnd = false;
708 	char szKeyword[11]="";
709 	int	 szKeywordlen = 0;
710 	char szThen[5]="";
711 	int	 szThenlen = 0;
712 	bool ThenFoundLast = false;
713 	// var for indentlevel
714 	int levelCurrent = SC_FOLDLEVELBASE;
715 	if (lineCurrent > 0)
716 		levelCurrent = styler.LevelAt(lineCurrent-1) >> 16;
717 	int levelNext = levelCurrent;
718 	//
719 	int	visibleChars = 0;
720 	char chNext = styler.SafeGetCharAt(startPos);
721 	char chPrev = ' ';
722 	//
723 	for (int i = startPos; i < endPos; i++) {
724 		char ch = chNext;
725 		chNext = styler.SafeGetCharAt(i + 1);
726 		if (IsAWordChar(ch)) {
727 			visibleChars++;
728 		}
729 		// get the syle for the current character neede to check in comment
730 		int stylech = styler.StyleAt(i);
731 		// get first word for the line for indent check max 9 characters
732 		if (FirstWordStart && (!(FirstWordEnd))) {
733 			if (!IsAWordChar(ch)) {
734 				FirstWordEnd = true;
735 				szKeyword[szKeywordlen] = '\0';
736 			}
737 			else {
738 				if (szKeywordlen < 10) {
739 				szKeyword[szKeywordlen++] = static_cast<char>(tolower(ch));
740 				}
741 			}
742 		}
743 		// start the capture of the first word
744 		if (!(FirstWordStart)) {
745 			if (IsAWordChar(ch) || IsAWordStart(ch) || ch == ';') {
746 				FirstWordStart = true;
747 				szKeyword[szKeywordlen++] = static_cast<char>(tolower(ch));
748 			}
749 		}
750 		// only process this logic when not in comment section
751 		if (!(stylech == SCE_AU3_COMMENT)) {
752 			if (ThenFoundLast) {
753 				if (IsAWordChar(ch)) {
754 					ThenFoundLast = false;
755 				}
756 			}
757 			// find out if the word "then" is the last on a "if" line
758 			if (FirstWordEnd && strcmp(szKeyword,"if") == 0) {
759 				if (szThenlen == 4) {
760 					szThen[0] = szThen[1];
761 					szThen[1] = szThen[2];
762 					szThen[2] = szThen[3];
763 					szThen[3] = static_cast<char>(tolower(ch));
764 					if (strcmp(szThen,"then") == 0 ) {
765 						ThenFoundLast = true;
766 					}
767 				}
768 				else {
769 					szThen[szThenlen++] = static_cast<char>(tolower(ch));
770 					if (szThenlen == 5) {
771 						szThen[4] = '\0';
772 					}
773 				}
774 			}
775 		}
776 		// End of Line found so process the information
777 		if ((ch == '\r' && chNext != '\n') || (ch == '\n') || (i == endPos)) {
778 			// **************************
779 			// Folding logic for Keywords
780 			// **************************
781 			// if a keyword is found on the current line and the line doesn't end with _ (continuation)
782 			//    and we are not inside a commentblock.
783 			if (szKeywordlen > 0 && (!(chPrev == '_')) &&
784 				((!(IsStreamCommentStyle(style)) || foldInComment)) ) {
785 				szKeyword[szKeywordlen] = '\0';
786 				// only fold "if" last keyword is "then"  (else its a one line if)
787 				if (strcmp(szKeyword,"if") == 0  && ThenFoundLast) {
788 						levelNext++;
789 				}
790 				// create new fold for these words
791 				if (strcmp(szKeyword,"do") == 0   || strcmp(szKeyword,"for") == 0 ||
792 					strcmp(szKeyword,"func") == 0 || strcmp(szKeyword,"while") == 0||
793 					strcmp(szKeyword,"with") == 0 || strcmp(szKeyword,"#region") == 0 ) {
794 						levelNext++;
795 				}
796 				// create double Fold for select&switch because Case will subtract one of the current level
797 				if (strcmp(szKeyword,"select") == 0 || strcmp(szKeyword,"switch") == 0) {
798 						levelNext++;
799 						levelNext++;
800 				}
801 				// end the fold for these words before the current line
802 				if (strcmp(szKeyword,"endfunc") == 0 || strcmp(szKeyword,"endif") == 0 ||
803 					strcmp(szKeyword,"next") == 0    || strcmp(szKeyword,"until") == 0 ||
804 					strcmp(szKeyword,"endwith") == 0 ||strcmp(szKeyword,"wend") == 0){
805 						levelNext--;
806 						levelCurrent--;
807 				}
808 				// end the fold for these words before the current line and Start new fold
809 				if (strcmp(szKeyword,"case") == 0      || strcmp(szKeyword,"else") == 0 ||
810 					strcmp(szKeyword,"elseif") == 0 ) {
811 						levelCurrent--;
812 				}
813 				// end the double fold for this word before the current line
814 				if (strcmp(szKeyword,"endselect") == 0 || strcmp(szKeyword,"endswitch") == 0 ) {
815 						levelNext--;
816 						levelNext--;
817 						levelCurrent--;
818 						levelCurrent--;
819 				}
820 				// end the fold for these words on the current line
821 				if (strcmp(szKeyword,"#endregion") == 0 ) {
822 						levelNext--;
823 				}
824 			}
825 			// Preprocessor and Comment folding
826 			int styleNext = GetStyleFirstWord(lineCurrent + 1,styler);
827 			// *************************************
828 			// Folding logic for preprocessor blocks
829 			// *************************************
830 			// process preprosessor line
831 			if (foldpreprocessor && style == SCE_AU3_PREPROCESSOR) {
832 				if (!(stylePrev == SCE_AU3_PREPROCESSOR) && (styleNext == SCE_AU3_PREPROCESSOR)) {
833 				    levelNext++;
834 				}
835 				// fold till the last line for normal comment lines
836 				else if (stylePrev == SCE_AU3_PREPROCESSOR && !(styleNext == SCE_AU3_PREPROCESSOR)) {
837 					levelNext--;
838 				}
839 			}
840 			// *********************************
841 			// Folding logic for Comment blocks
842 			// *********************************
843 			if (foldComment && IsStreamCommentStyle(style)) {
844 				// Start of a comment block
845 				if (!(stylePrev==style) && IsStreamCommentStyle(styleNext) && styleNext==style) {
846 				    levelNext++;
847 				}
848 				// fold till the last line for normal comment lines
849 				else if (IsStreamCommentStyle(stylePrev)
850 						&& !(styleNext == SCE_AU3_COMMENT)
851 						&& stylePrev == SCE_AU3_COMMENT
852 						&& style == SCE_AU3_COMMENT) {
853 					levelNext--;
854 				}
855 				// fold till the one but last line for Blockcomment lines
856 				else if (IsStreamCommentStyle(stylePrev)
857 						&& !(styleNext == SCE_AU3_COMMENTBLOCK)
858 						&& style == SCE_AU3_COMMENTBLOCK) {
859 					levelNext--;
860 					levelCurrent--;
861 				}
862 			}
863 			int levelUse = levelCurrent;
864 			int lev = levelUse | levelNext << 16;
865 			if (visibleChars == 0 && foldCompact)
866 				lev |= SC_FOLDLEVELWHITEFLAG;
867 			if (levelUse < levelNext) {
868 				lev |= SC_FOLDLEVELHEADERFLAG;
869 			}
870 			if (lev != styler.LevelAt(lineCurrent)) {
871 				styler.SetLevel(lineCurrent, lev);
872 			}
873 			// reset values for the next line
874 			lineCurrent++;
875 			stylePrev = style;
876 			style = styleNext;
877 			levelCurrent = levelNext;
878 			visibleChars = 0;
879 			// if the last character is an Underscore then don't reset since the line continues on the next line.
880 			if (!(chPrev == '_')) {
881 				szKeywordlen = 0;
882 				szThenlen = 0;
883 				FirstWordStart = false;
884 				FirstWordEnd = false;
885 				ThenFoundLast = false;
886 			}
887 		}
888 		// save the last processed character
889 		if (!isspacechar(ch)) {
890 			chPrev = ch;
891 			visibleChars++;
892 		}
893 	}
894 }
895 
896 
897 //
898 
899 static const char * const AU3WordLists[] = {
900     "#autoit keywords",
901     "#autoit functions",
902     "#autoit macros",
903     "#autoit Sent keys",
904     "#autoit Pre-processors",
905     "#autoit Special",
906     "#autoit Expand",
907     "#autoit UDF",
908     0
909 };
910 LexerModule lmAU3(SCLEX_AU3, ColouriseAU3Doc, "au3", FoldAU3Doc , AU3WordLists);
911