PageRenderTime 58ms CodeModel.GetById 15ms app.highlight 37ms RepoModel.GetById 1ms app.codeStats 0ms

/cssed-0.4.0/scintilla/src/LexBash.cxx

#
C++ | 661 lines | 607 code | 28 blank | 26 comment | 166 complexity | 71b792ab0cd2c8e5ea08bc6e121d2b57 MD5 | raw file
  1// Scintilla source code edit control
  2/** @file LexBash.cxx
  3 ** Lexer for Bash.
  4 **/
  5// Copyright 2004-2005 by Neil Hodgson <neilh@scintilla.org>
  6// Adapted from LexPerl by Kein-Hong Man <mkh@pl.jaring.my> 2004
  7// The License.txt file describes the conditions under which this software may be distributed.
  8
  9#include <stdlib.h>

 10#include <string.h>

 11#include <ctype.h>

 12#include <stdio.h>

 13#include <stdarg.h>

 14
 15#include "Platform.h"

 16
 17#include "PropSet.h"

 18#include "Accessor.h"

 19#include "KeyWords.h"

 20#include "Scintilla.h"

 21#include "SciLexer.h"

 22
 23#define BASH_BASE_ERROR		65

 24#define BASH_BASE_DECIMAL	66

 25#define BASH_BASE_HEX		67

 26#define BASH_BASE_OCTAL		68

 27#define BASH_BASE_OCTAL_ERROR	69

 28
 29#define HERE_DELIM_MAX 256

 30
 31static inline int translateBashDigit(char ch) {
 32	if (ch >= '0' && ch <= '9') {
 33		return ch - '0';
 34	} else if (ch >= 'a' && ch <= 'z') {
 35		return ch - 'a' + 10;
 36	} else if (ch >= 'A' && ch <= 'Z') {
 37		return ch - 'A' + 36;
 38	} else if (ch == '@') {
 39		return 62;
 40	} else if (ch == '_') {
 41		return 63;
 42	}
 43	return BASH_BASE_ERROR;
 44}
 45
 46static inline bool isEOLChar(char ch) {
 47	return (ch == '\r') || (ch == '\n');
 48}
 49
 50static bool isSingleCharOp(char ch) {
 51	char strCharSet[2];
 52	strCharSet[0] = ch;
 53	strCharSet[1] = '\0';
 54	return (NULL != strstr("rwxoRWXOezsfdlpSbctugkTBMACahGLNn", strCharSet));
 55}
 56
 57static inline bool isBashOperator(char ch) {
 58	if (ch == '^' || ch == '&' || ch == '\\' || ch == '%' ||
 59	        ch == '(' || ch == ')' || ch == '-' || ch == '+' ||
 60	        ch == '=' || ch == '|' || ch == '{' || ch == '}' ||
 61	        ch == '[' || ch == ']' || ch == ':' || ch == ';' ||
 62	        ch == '>' || ch == ',' || ch == '/' || ch == '<' ||
 63	        ch == '?' || ch == '!' || ch == '.' || ch == '~' ||
 64		ch == '@')
 65		return true;
 66	return false;
 67}
 68
 69static int classifyWordBash(unsigned int start, unsigned int end, WordList &keywords, Accessor &styler) {
 70	char s[100];
 71	for (unsigned int i = 0; i < end - start + 1 && i < 30; i++) {
 72		s[i] = styler[start + i];
 73		s[i + 1] = '\0';
 74	}
 75	char chAttr = SCE_SH_IDENTIFIER;
 76	if (keywords.InList(s))
 77		chAttr = SCE_SH_WORD;
 78	styler.ColourTo(end, chAttr);
 79	return chAttr;
 80}
 81
 82static inline int getBashNumberBase(unsigned int start, unsigned int end, Accessor &styler) {
 83	int base = 0;
 84	for (unsigned int i = 0; i < end - start + 1 && i < 10; i++) {
 85		base = base * 10 + (styler[start + i] - '0');
 86	}
 87	if (base > 64 || (end - start) > 1) {
 88		return BASH_BASE_ERROR;
 89	}
 90	return base;
 91}
 92
 93static inline bool isEndVar(char ch) {
 94	return !isalnum(ch) && ch != '$' && ch != '_';
 95}
 96
 97static inline bool isNonQuote(char ch) {
 98	return isalnum(ch) || ch == '_';
 99}
100
101static bool isMatch(Accessor &styler, int lengthDoc, int pos, const char *val) {
102	if ((pos + static_cast<int>(strlen(val))) >= lengthDoc) {
103		return false;
104	}
105	while (*val) {
106		if (*val != styler[pos++]) {
107			return false;
108		}
109		val++;
110	}
111	return true;
112}
113
114static char opposite(char ch) {
115	if (ch == '(')
116		return ')';
117	if (ch == '[')
118		return ']';
119	if (ch == '{')
120		return '}';
121	if (ch == '<')
122		return '>';
123	return ch;
124}
125
126static void ColouriseBashDoc(unsigned int startPos, int length, int initStyle,
127                             WordList *keywordlists[], Accessor &styler) {
128
129	// Lexer for bash often has to backtrack to start of current style to determine
130	// which characters are being used as quotes, how deeply nested is the
131	// start position and what the termination string is for here documents
132
133	WordList &keywords = *keywordlists[0];
134
135	class HereDocCls {
136	public:
137		int State;		// 0: '<<' encountered
138		// 1: collect the delimiter
139		// 2: here doc text (lines after the delimiter)
140		char Quote;		// the char after '<<'
141		bool Quoted;		// true if Quote in ('\'','"','`')
142		bool Indent;		// indented delimiter (for <<-)
143		int DelimiterLength;	// strlen(Delimiter)
144		char *Delimiter;	// the Delimiter, 256: sizeof PL_tokenbuf
145		HereDocCls() {
146			State = 0;
147            Quote = 0;
148            Quoted = false;
149            Indent = 0;
150			DelimiterLength = 0;
151			Delimiter = new char[HERE_DELIM_MAX];
152			Delimiter[0] = '\0';
153		}
154		~HereDocCls() {
155			delete []Delimiter;
156		}
157	};
158	HereDocCls HereDoc;
159
160	class QuoteCls {
161		public:
162		int  Rep;
163		int  Count;
164		char Up;
165		char Down;
166		QuoteCls() {
167			this->New(1);
168		}
169		void New(int r) {
170			Rep   = r;
171			Count = 0;
172			Up    = '\0';
173			Down  = '\0';
174		}
175		void Open(char u) {
176			Count++;
177			Up    = u;
178			Down  = opposite(Up);
179		}
180	};
181	QuoteCls Quote;
182
183	int state = initStyle;
184	int numBase = 0;
185	unsigned int lengthDoc = startPos + length;
186
187	// If in a long distance lexical state, seek to the beginning to find quote characters
188	// Bash strings can be multi-line with embedded newlines, so backtrack.
189	// Bash numbers have additional state during lexing, so backtrack too.
190	if (state == SCE_SH_HERE_Q) {
191		while ((startPos > 1) && (styler.StyleAt(startPos) != SCE_SH_HERE_DELIM)) {
192			startPos--;
193		}
194		startPos = styler.LineStart(styler.GetLine(startPos));
195		state = styler.StyleAt(startPos - 1);
196	}
197	if (state == SCE_SH_STRING
198	 || state == SCE_SH_BACKTICKS
199	 || state == SCE_SH_CHARACTER
200	 || state == SCE_SH_NUMBER
201	 || state == SCE_SH_IDENTIFIER
202	 || state == SCE_SH_COMMENTLINE
203	) {
204		while ((startPos > 1) && (styler.StyleAt(startPos - 1) == state)) {
205			startPos--;
206		}
207		state = SCE_SH_DEFAULT;
208	}
209
210	styler.StartAt(startPos);
211	char chPrev = styler.SafeGetCharAt(startPos - 1);
212	if (startPos == 0)
213		chPrev = '\n';
214	char chNext = styler[startPos];
215	styler.StartSegment(startPos);
216
217	for (unsigned int i = startPos; i < lengthDoc; i++) {
218		char ch = chNext;
219		// if the current character is not consumed due to the completion of an
220		// earlier style, lexing can be restarted via a simple goto
221	restartLexer:
222		chNext = styler.SafeGetCharAt(i + 1);
223		char chNext2 = styler.SafeGetCharAt(i + 2);
224
225		if (styler.IsLeadByte(ch)) {
226			chNext = styler.SafeGetCharAt(i + 2);
227			chPrev = ' ';
228			i += 1;
229			continue;
230		}
231
232		if ((chPrev == '\r' && ch == '\n')) {	// skip on DOS/Windows
233			styler.ColourTo(i, state);
234			chPrev = ch;
235			continue;
236		}
237
238		if (HereDoc.State == 1 && isEOLChar(ch)) {
239			// Begin of here-doc (the line after the here-doc delimiter):
240			// Lexically, the here-doc starts from the next line after the >>, but the
241			// first line of here-doc seem to follow the style of the last EOL sequence
242			HereDoc.State = 2;
243			if (HereDoc.Quoted) {
244				if (state == SCE_SH_HERE_DELIM) {
245					// Missing quote at end of string! We are stricter than bash.
246					// Colour here-doc anyway while marking this bit as an error.
247					state = SCE_SH_ERROR;
248				}
249				styler.ColourTo(i - 1, state);
250				// HereDoc.Quote always == '\''
251				state = SCE_SH_HERE_Q;
252			} else {
253				styler.ColourTo(i - 1, state);
254				// always switch
255				state = SCE_SH_HERE_Q;
256			}
257		}
258
259		if (state == SCE_SH_DEFAULT) {
260			if (ch == '\\') {	// escaped character
261				i++;
262				ch = chNext;
263				chNext = chNext2;
264				styler.ColourTo(i, SCE_SH_IDENTIFIER);
265			} else if (isdigit(ch)) {
266				state = SCE_SH_NUMBER;
267				numBase = BASH_BASE_DECIMAL;
268				if (ch == '0') {	// hex,octal
269					if (chNext == 'x' || chNext == 'X') {
270						numBase = BASH_BASE_HEX;
271						i++;
272						ch = chNext;
273						chNext = chNext2;
274					} else if (isdigit(chNext)) {
275						numBase = BASH_BASE_OCTAL;
276					}
277				}
278			} else if (iswordstart(ch)) {
279				state = SCE_SH_WORD;
280				if (!iswordchar(chNext) && chNext != '+' && chNext != '-') {
281					// We need that if length of word == 1!
282					// This test is copied from the SCE_SH_WORD handler.
283					classifyWordBash(styler.GetStartSegment(), i, keywords, styler);
284					state = SCE_SH_DEFAULT;
285				}
286			} else if (ch == '#') {
287				state = SCE_SH_COMMENTLINE;
288			} else if (ch == '\"') {
289				state = SCE_SH_STRING;
290				Quote.New(1);
291				Quote.Open(ch);
292			} else if (ch == '\'') {
293				state = SCE_SH_CHARACTER;
294				Quote.New(1);
295				Quote.Open(ch);
296			} else if (ch == '`') {
297				state = SCE_SH_BACKTICKS;
298				Quote.New(1);
299				Quote.Open(ch);
300			} else if (ch == '$') {
301				if (chNext == '{') {
302					state = SCE_SH_PARAM;
303					goto startQuote;
304				} else if (chNext == '\'') {
305					state = SCE_SH_CHARACTER;
306					goto startQuote;
307				} else if (chNext == '"') {
308					state = SCE_SH_STRING;
309					goto startQuote;
310				} else if (chNext == '(' && chNext2 == '(') {
311					styler.ColourTo(i, SCE_SH_OPERATOR);
312					state = SCE_SH_DEFAULT;
313					goto skipChar;
314				} else if (chNext == '(' || chNext == '`') {
315					state = SCE_SH_BACKTICKS;
316				startQuote:
317					Quote.New(1);
318					Quote.Open(chNext);
319					goto skipChar;
320				} else {
321					state = SCE_SH_SCALAR;
322				skipChar:
323					i++;
324					ch = chNext;
325					chNext = chNext2;
326				}
327			} else if (ch == '*') {
328				if (chNext == '*') {	// exponentiation
329					i++;
330					ch = chNext;
331					chNext = chNext2;
332				}
333				styler.ColourTo(i, SCE_SH_OPERATOR);
334			} else if (ch == '<' && chNext == '<') {
335				state = SCE_SH_HERE_DELIM;
336				HereDoc.State = 0;
337				HereDoc.Indent = false;
338			} else if (ch == '-'	// file test operators
339			           && isSingleCharOp(chNext)
340			           && !isalnum((chNext2 = styler.SafeGetCharAt(i+2)))) {
341				styler.ColourTo(i + 1, SCE_SH_WORD);
342				state = SCE_SH_DEFAULT;
343				i++;
344				ch = chNext;
345				chNext = chNext2;
346			} else if (isBashOperator(ch)) {
347				styler.ColourTo(i, SCE_SH_OPERATOR);
348			} else {
349				// keep colouring defaults to make restart easier
350				styler.ColourTo(i, SCE_SH_DEFAULT);
351			}
352		} else if (state == SCE_SH_NUMBER) {
353			int digit = translateBashDigit(ch);
354			if (numBase == BASH_BASE_DECIMAL) {
355				if (ch == '#') {
356					numBase = getBashNumberBase(styler.GetStartSegment(), i - 1, styler);
357					if (numBase == BASH_BASE_ERROR)	// take the rest as comment
358						goto numAtEnd;
359				} else if (!isdigit(ch))
360					goto numAtEnd;
361			} else if (numBase == BASH_BASE_HEX) {
362				if ((digit < 16) || (digit >= 36 && digit <= 41)) {
363					// hex digit 0-9a-fA-F
364				} else
365					goto numAtEnd;
366			} else if (numBase == BASH_BASE_OCTAL ||
367				   numBase == BASH_BASE_OCTAL_ERROR) {
368				if (digit > 7) {
369					if (digit <= 9) {
370						numBase = BASH_BASE_OCTAL_ERROR;
371					} else
372						goto numAtEnd;
373				}
374			} else if (numBase == BASH_BASE_ERROR) {
375				if (digit > 9)
376					goto numAtEnd;
377			} else {	// DD#DDDD number style handling
378				if (digit != BASH_BASE_ERROR) {
379					if (numBase <= 36) {
380						// case-insensitive if base<=36
381						if (digit >= 36) digit -= 26;
382					}
383					if (digit >= numBase) {
384						if (digit <= 9) {
385							numBase = BASH_BASE_ERROR;
386						} else
387							goto numAtEnd;
388					}
389				} else {
390			numAtEnd:
391					if (numBase == BASH_BASE_ERROR ||
392					    numBase == BASH_BASE_OCTAL_ERROR)
393						state = SCE_SH_ERROR;
394					styler.ColourTo(i - 1, state);
395					state = SCE_SH_DEFAULT;
396					goto restartLexer;
397				}
398			}
399		} else if (state == SCE_SH_WORD) {
400			if (!iswordchar(chNext) && chNext != '+' && chNext != '-') {
401				// "." never used in Bash variable names
402				// but used in file names
403				classifyWordBash(styler.GetStartSegment(), i, keywords, styler);
404				state = SCE_SH_DEFAULT;
405				ch = ' ';
406			}
407		} else if (state == SCE_SH_IDENTIFIER) {
408			if (!iswordchar(chNext) && chNext != '+' && chNext != '-') {
409				styler.ColourTo(i, SCE_SH_IDENTIFIER);
410				state = SCE_SH_DEFAULT;
411				ch = ' ';
412			}
413		} else {
414			if (state == SCE_SH_COMMENTLINE) {
415				if (ch == '\\' && isEOLChar(chNext)) {
416					// comment continuation
417					if (chNext == '\r' && chNext2 == '\n') {
418						i += 2;
419						ch = styler.SafeGetCharAt(i);
420						chNext = styler.SafeGetCharAt(i + 1);
421					} else {
422						i++;
423						ch = chNext;
424						chNext = chNext2;
425					}
426				} else if (isEOLChar(ch)) {
427					styler.ColourTo(i - 1, state);
428					state = SCE_SH_DEFAULT;
429					goto restartLexer;
430				} else if (isEOLChar(chNext)) {
431					styler.ColourTo(i, state);
432					state = SCE_SH_DEFAULT;
433				}
434			} else if (state == SCE_SH_HERE_DELIM) {
435				//
436				// From Bash info:
437				// ---------------
438				// Specifier format is: <<[-]WORD
439				// Optional '-' is for removal of leading tabs from here-doc.
440				// Whitespace acceptable after <<[-] operator
441				//
442				if (HereDoc.State == 0) { // '<<' encountered
443					HereDoc.State = 1;
444					HereDoc.Quote = chNext;
445					HereDoc.Quoted = false;
446					HereDoc.DelimiterLength = 0;
447					HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
448					if (chNext == '\'') {	// a quoted here-doc delimiter (' only)
449						i++;
450						ch = chNext;
451						chNext = chNext2;
452						HereDoc.Quoted = true;
453					} else if (!HereDoc.Indent && chNext == '-') {	// <<- indent case
454						HereDoc.Indent = true;
455						HereDoc.State = 0;
456					} else if (isalpha(chNext) || chNext == '_' || chNext == '\\'
457						|| chNext == '-' || chNext == '+') {
458						// an unquoted here-doc delimiter, no special handling
459					} else if (chNext == '<') {	// HERE string <<<
460						i++;
461						ch = chNext;
462						chNext = chNext2;
463						styler.ColourTo(i, SCE_SH_HERE_DELIM);
464						state = SCE_SH_DEFAULT;
465						HereDoc.State = 0;
466					} else if (isspacechar(chNext)) {
467						// eat whitespace
468						HereDoc.State = 0;
469					} else if (isdigit(chNext) || chNext == '=' || chNext == '$') {
470						// left shift << or <<= operator cases
471						styler.ColourTo(i, SCE_SH_OPERATOR);
472						state = SCE_SH_DEFAULT;
473						HereDoc.State = 0;
474					} else {
475						// symbols terminates; deprecated zero-length delimiter
476					}
477				} else if (HereDoc.State == 1) { // collect the delimiter
478					if (HereDoc.Quoted) { // a quoted here-doc delimiter
479						if (ch == HereDoc.Quote) { // closing quote => end of delimiter
480							styler.ColourTo(i, state);
481							state = SCE_SH_DEFAULT;
482						} else {
483							if (ch == '\\' && chNext == HereDoc.Quote) { // escaped quote
484								i++;
485								ch = chNext;
486								chNext = chNext2;
487							}
488							HereDoc.Delimiter[HereDoc.DelimiterLength++] = ch;
489							HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
490						}
491					} else { // an unquoted here-doc delimiter
492						if (isalnum(ch) || ch == '_' || ch == '-' || ch == '+') {
493							HereDoc.Delimiter[HereDoc.DelimiterLength++] = ch;
494							HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
495						} else if (ch == '\\') {
496							// skip escape prefix
497						} else {
498							styler.ColourTo(i - 1, state);
499							state = SCE_SH_DEFAULT;
500							goto restartLexer;
501						}
502					}
503					if (HereDoc.DelimiterLength >= HERE_DELIM_MAX - 1) {
504						styler.ColourTo(i - 1, state);
505						state = SCE_SH_ERROR;
506						goto restartLexer;
507					}
508				}
509			} else if (HereDoc.State == 2) {
510				// state == SCE_SH_HERE_Q
511				if (isMatch(styler, lengthDoc, i, HereDoc.Delimiter)) {
512					if (!HereDoc.Indent && isEOLChar(chPrev)) {
513					endHereDoc:
514						// standard HERE delimiter
515						i += HereDoc.DelimiterLength;
516						chPrev = styler.SafeGetCharAt(i - 1);
517						ch = styler.SafeGetCharAt(i);
518						if (isEOLChar(ch)) {
519							styler.ColourTo(i - 1, state);
520							state = SCE_SH_DEFAULT;
521							HereDoc.State = 0;
522							goto restartLexer;
523						}
524						chNext = styler.SafeGetCharAt(i + 1);
525					} else if (HereDoc.Indent) {
526						// indented HERE delimiter
527						unsigned int bk = (i > 0)? i - 1: 0;
528						while (i > 0) {
529							ch = styler.SafeGetCharAt(bk--);
530							if (isEOLChar(ch)) {
531								goto endHereDoc;
532							} else if (!isspacechar(ch)) {
533								break;	// got leading non-whitespace
534							}
535						}
536					}
537				}
538			} else if (state == SCE_SH_SCALAR) {	// variable names
539				if (isEndVar(ch)) {
540					if ((state == SCE_SH_SCALAR)
541					    && i == (styler.GetStartSegment() + 1)) {
542						// Special variable: $(, $_ etc.
543						styler.ColourTo(i, state);
544						state = SCE_SH_DEFAULT;
545					} else {
546						styler.ColourTo(i - 1, state);
547						state = SCE_SH_DEFAULT;
548						goto restartLexer;
549					}
550				}
551			} else if (state == SCE_SH_STRING
552				|| state == SCE_SH_CHARACTER
553				|| state == SCE_SH_BACKTICKS
554				|| state == SCE_SH_PARAM
555				) {
556				if (!Quote.Down && !isspacechar(ch)) {
557					Quote.Open(ch);
558				} else if (ch == '\\' && Quote.Up != '\\') {
559					i++;
560					ch = chNext;
561					chNext = styler.SafeGetCharAt(i + 1);
562				} else if (ch == Quote.Down) {
563					Quote.Count--;
564					if (Quote.Count == 0) {
565						Quote.Rep--;
566						if (Quote.Rep <= 0) {
567							styler.ColourTo(i, state);
568							state = SCE_SH_DEFAULT;
569							ch = ' ';
570						}
571						if (Quote.Up == Quote.Down) {
572							Quote.Count++;
573						}
574					}
575				} else if (ch == Quote.Up) {
576					Quote.Count++;
577				}
578			}
579		}
580		if (state == SCE_SH_ERROR) {
581			break;
582		}
583		chPrev = ch;
584	}
585	styler.ColourTo(lengthDoc - 1, state);
586}
587
588static bool IsCommentLine(int line, Accessor &styler) {
589	int pos = styler.LineStart(line);
590	int eol_pos = styler.LineStart(line + 1) - 1;
591	for (int i = pos; i < eol_pos; i++) {
592		char ch = styler[i];
593		if (ch == '#')
594			return true;
595		else if (ch != ' ' && ch != '\t')
596			return false;
597	}
598	return false;
599}
600
601static void FoldBashDoc(unsigned int startPos, int length, int, WordList *[],
602                            Accessor &styler) {
603	bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
604	bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
605	unsigned int endPos = startPos + length;
606	int visibleChars = 0;
607	int lineCurrent = styler.GetLine(startPos);
608	int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
609	int levelCurrent = levelPrev;
610	char chNext = styler[startPos];
611	int styleNext = styler.StyleAt(startPos);
612	for (unsigned int i = startPos; i < endPos; i++) {
613		char ch = chNext;
614		chNext = styler.SafeGetCharAt(i + 1);
615		int style = styleNext;
616		styleNext = styler.StyleAt(i + 1);
617		bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
618        // Comment folding
619		if (foldComment && atEOL && IsCommentLine(lineCurrent, styler))
620        {
621            if (!IsCommentLine(lineCurrent - 1, styler)
622                && IsCommentLine(lineCurrent + 1, styler))
623                levelCurrent++;
624            else if (IsCommentLine(lineCurrent - 1, styler)
625                     && !IsCommentLine(lineCurrent+1, styler))
626                levelCurrent--;
627        }
628		if (style == SCE_C_OPERATOR) {
629			if (ch == '{') {
630				levelCurrent++;
631			} else if (ch == '}') {
632				levelCurrent--;
633			}
634		}
635		if (atEOL) {
636			int lev = levelPrev;
637			if (visibleChars == 0 && foldCompact)
638				lev |= SC_FOLDLEVELWHITEFLAG;
639			if ((levelCurrent > levelPrev) && (visibleChars > 0))
640				lev |= SC_FOLDLEVELHEADERFLAG;
641			if (lev != styler.LevelAt(lineCurrent)) {
642				styler.SetLevel(lineCurrent, lev);
643			}
644			lineCurrent++;
645			levelPrev = levelCurrent;
646			visibleChars = 0;
647		}
648		if (!isspacechar(ch))
649			visibleChars++;
650	}
651	// Fill in the real level of the next line, keeping the current flags as they will be filled in later
652	int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
653	styler.SetLevel(lineCurrent, levelPrev | flagsNext);
654}
655
656static const char * const bashWordListDesc[] = {
657	"Keywords",
658	0
659};
660
661LexerModule lmBash(SCLEX_BASH, ColouriseBashDoc, "bash", FoldBashDoc, bashWordListDesc);