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