]> git.saurik.com Git - wxWidgets.git/blame - src/stc/scintilla/lexers/LexBash.cxx
Add missing WXK constants for the control keys
[wxWidgets.git] / src / stc / scintilla / lexers / LexBash.cxx
CommitLineData
591d01be
RD
1// Scintilla source code edit control
2/** @file LexBash.cxx
3 ** Lexer for Bash.
4 **/
1dcf666d 5// Copyright 2004-2010 by Neil Hodgson <neilh@scintilla.org>
9e96e16f 6// Adapted from LexPerl by Kein-Hong Man 2004
591d01be
RD
7// The License.txt file describes the conditions under which this software may be distributed.
8
9#include <stdlib.h>
10#include <string.h>
591d01be
RD
11#include <stdio.h>
12#include <stdarg.h>
1dcf666d 13#include <assert.h>
591d01be 14
1dcf666d
RD
15#include "ILexer.h"
16#include "Scintilla.h"
17#include "SciLexer.h"
591d01be 18
1dcf666d
RD
19#include "WordList.h"
20#include "LexAccessor.h"
591d01be 21#include "Accessor.h"
9e96e16f 22#include "StyleContext.h"
9e96e16f 23#include "CharacterSet.h"
1dcf666d 24#include "LexerModule.h"
9e96e16f
RD
25
26#ifdef SCI_NAMESPACE
27using namespace Scintilla;
28#endif
29
1dcf666d 30#define HERE_DELIM_MAX 256
591d01be 31
7e0c58e9
RD
32// define this if you want 'invalid octals' to be marked as errors
33// usually, this is not a good idea, permissive lexing is better
34#undef PEDANTIC_OCTAL
35
1dcf666d
RD
36#define BASH_BASE_ERROR 65
37#define BASH_BASE_DECIMAL 66
38#define BASH_BASE_HEX 67
7e0c58e9 39#ifdef PEDANTIC_OCTAL
1dcf666d
RD
40#define BASH_BASE_OCTAL 68
41#define BASH_BASE_OCTAL_ERROR 69
7e0c58e9 42#endif
591d01be 43
1dcf666d
RD
44// state constants for parts of a bash command segment
45#define BASH_CMD_BODY 0
46#define BASH_CMD_START 1
47#define BASH_CMD_WORD 2
48#define BASH_CMD_TEST 3
49#define BASH_CMD_ARITH 4
50#define BASH_CMD_DELIM 5
51
9e96e16f 52static inline int translateBashDigit(int ch) {
591d01be
RD
53 if (ch >= '0' && ch <= '9') {
54 return ch - '0';
55 } else if (ch >= 'a' && ch <= 'z') {
56 return ch - 'a' + 10;
57 } else if (ch >= 'A' && ch <= 'Z') {
58 return ch - 'A' + 36;
59 } else if (ch == '@') {
60 return 62;
61 } else if (ch == '_') {
62 return 63;
63 }
64 return BASH_BASE_ERROR;
65}
66
9e96e16f
RD
67static inline int getBashNumberBase(char *s) {
68 int i = 0;
591d01be 69 int base = 0;
9e96e16f
RD
70 while (*s) {
71 base = base * 10 + (*s++ - '0');
72 i++;
591d01be 73 }
9e96e16f 74 if (base > 64 || i > 2) {
591d01be
RD
75 return BASH_BASE_ERROR;
76 }
77 return base;
78}
79
9e96e16f
RD
80static int opposite(int ch) {
81 if (ch == '(') return ')';
82 if (ch == '[') return ']';
83 if (ch == '{') return '}';
84 if (ch == '<') return '>';
591d01be
RD
85 return ch;
86}
87
88static void ColouriseBashDoc(unsigned int startPos, int length, int initStyle,
9e96e16f 89 WordList *keywordlists[], Accessor &styler) {
591d01be
RD
90
91 WordList &keywords = *keywordlists[0];
1dcf666d
RD
92 WordList cmdDelimiter, bashStruct, bashStruct_in;
93 cmdDelimiter.Set("| || |& & && ; ;; ( ) { }");
94 bashStruct.Set("if elif fi while until else then do done esac eval");
95 bashStruct_in.Set("for case select");
591d01be 96
9e96e16f
RD
97 CharacterSet setWordStart(CharacterSet::setAlpha, "_");
98 // note that [+-] are often parts of identifiers in shell scripts
99 CharacterSet setWord(CharacterSet::setAlphaNum, "._+-");
1dcf666d 100 CharacterSet setBashOperator(CharacterSet::setNone, "^&%()-+=|{}[]:;>,*/<?!.~@");
9e96e16f
RD
101 CharacterSet setSingleCharOp(CharacterSet::setNone, "rwxoRWXOezsfdlpSbctugkTBMACahGLNn");
102 CharacterSet setParam(CharacterSet::setAlphaNum, "$_");
103 CharacterSet setHereDoc(CharacterSet::setAlpha, "_\\-+!");
104 CharacterSet setHereDoc2(CharacterSet::setAlphaNum, "_-+!");
105 CharacterSet setLeftShift(CharacterSet::setDigits, "=$");
106
107 class HereDocCls { // Class to manage HERE document elements
591d01be
RD
108 public:
109 int State; // 0: '<<' encountered
110 // 1: collect the delimiter
111 // 2: here doc text (lines after the delimiter)
9e96e16f 112 int Quote; // the char after '<<'
591d01be
RD
113 bool Quoted; // true if Quote in ('\'','"','`')
114 bool Indent; // indented delimiter (for <<-)
115 int DelimiterLength; // strlen(Delimiter)
116 char *Delimiter; // the Delimiter, 256: sizeof PL_tokenbuf
117 HereDocCls() {
118 State = 0;
9e96e16f
RD
119 Quote = 0;
120 Quoted = false;
121 Indent = 0;
591d01be
RD
122 DelimiterLength = 0;
123 Delimiter = new char[HERE_DELIM_MAX];
124 Delimiter[0] = '\0';
125 }
9e96e16f
RD
126 void Append(int ch) {
127 Delimiter[DelimiterLength++] = static_cast<char>(ch);
128 Delimiter[DelimiterLength] = '\0';
129 }
591d01be
RD
130 ~HereDocCls() {
131 delete []Delimiter;
132 }
133 };
134 HereDocCls HereDoc;
135
9e96e16f 136 class QuoteCls { // Class to manage quote pairs (simplified vs LexPerl)
591d01be 137 public:
9e96e16f
RD
138 int Count;
139 int Up, Down;
591d01be 140 QuoteCls() {
591d01be
RD
141 Count = 0;
142 Up = '\0';
143 Down = '\0';
144 }
9e96e16f 145 void Open(int u) {
591d01be
RD
146 Count++;
147 Up = u;
148 Down = opposite(Up);
149 }
9e96e16f
RD
150 void Start(int u) {
151 Count = 0;
152 Open(u);
153 }
591d01be
RD
154 };
155 QuoteCls Quote;
156
591d01be 157 int numBase = 0;
9e96e16f
RD
158 int digit;
159 unsigned int endPos = startPos + length;
1dcf666d
RD
160 int cmdState = BASH_CMD_START;
161 int testExprType = 0;
591d01be 162
1dcf666d
RD
163 // Always backtracks to the start of a line that is not a continuation
164 // of the previous line (i.e. start of a bash command segment)
165 int ln = styler.GetLine(startPos);
166 for (;;) {
167 startPos = styler.LineStart(ln);
168 if (ln == 0 || styler.GetLineState(ln) == BASH_CMD_START)
169 break;
170 ln--;
591d01be 171 }
1dcf666d 172 initStyle = SCE_SH_DEFAULT;
591d01be 173
9e96e16f
RD
174 StyleContext sc(startPos, endPos - startPos, initStyle, styler);
175
176 for (; sc.More(); sc.Forward()) {
177
1dcf666d
RD
178 // handle line continuation, updates per-line stored state
179 if (sc.atLineStart) {
180 ln = styler.GetLine(sc.currentPos);
181 if (sc.state == SCE_SH_STRING
182 || sc.state == SCE_SH_BACKTICKS
183 || sc.state == SCE_SH_CHARACTER
184 || sc.state == SCE_SH_HERE_Q
185 || sc.state == SCE_SH_COMMENTLINE
186 || sc.state == SCE_SH_PARAM) {
187 // force backtrack while retaining cmdState
188 styler.SetLineState(ln, BASH_CMD_BODY);
189 } else {
190 if (ln > 0) {
191 if ((sc.GetRelative(-3) == '\\' && sc.GetRelative(-2) == '\r' && sc.chPrev == '\n')
192 || sc.GetRelative(-2) == '\\') { // handle '\' line continuation
193 // retain last line's state
194 } else
195 cmdState = BASH_CMD_START;
196 }
197 styler.SetLineState(ln, cmdState);
198 }
199 }
200
201 // controls change of cmdState at the end of a non-whitespace element
202 // states BODY|TEST|ARITH persist until the end of a command segment
203 // state WORD persist, but ends with 'in' or 'do' construct keywords
204 int cmdStateNew = BASH_CMD_BODY;
205 if (cmdState == BASH_CMD_TEST || cmdState == BASH_CMD_ARITH || cmdState == BASH_CMD_WORD)
206 cmdStateNew = cmdState;
207 int stylePrev = sc.state;
208
9e96e16f
RD
209 // Determine if the current state should terminate.
210 switch (sc.state) {
211 case SCE_SH_OPERATOR:
212 sc.SetState(SCE_SH_DEFAULT);
1dcf666d
RD
213 if (cmdState == BASH_CMD_DELIM) // if command delimiter, start new command
214 cmdStateNew = BASH_CMD_START;
215 else if (sc.chPrev == '\\') // propagate command state if line continued
216 cmdStateNew = cmdState;
9e96e16f
RD
217 break;
218 case SCE_SH_WORD:
219 // "." never used in Bash variable names but used in file names
220 if (!setWord.Contains(sc.ch)) {
1dcf666d
RD
221 char s[500];
222 char s2[10];
9e96e16f 223 sc.GetCurrent(s, sizeof(s));
1dcf666d
RD
224 // allow keywords ending in a whitespace or command delimiter
225 s2[0] = static_cast<char>(sc.ch);
226 s2[1] = '\0';
227 bool keywordEnds = IsASpace(sc.ch) || cmdDelimiter.InList(s2);
228 // 'in' or 'do' may be construct keywords
229 if (cmdState == BASH_CMD_WORD) {
230 if (strcmp(s, "in") == 0 && keywordEnds)
231 cmdStateNew = BASH_CMD_BODY;
232 else if (strcmp(s, "do") == 0 && keywordEnds)
233 cmdStateNew = BASH_CMD_START;
234 else
235 sc.ChangeState(SCE_SH_IDENTIFIER);
236 sc.SetState(SCE_SH_DEFAULT);
237 break;
238 }
239 // a 'test' keyword starts a test expression
240 if (strcmp(s, "test") == 0) {
241 if (cmdState == BASH_CMD_START && keywordEnds) {
242 cmdStateNew = BASH_CMD_TEST;
243 testExprType = 0;
244 } else
245 sc.ChangeState(SCE_SH_IDENTIFIER);
246 }
247 // detect bash construct keywords
248 else if (bashStruct.InList(s)) {
249 if (cmdState == BASH_CMD_START && keywordEnds)
250 cmdStateNew = BASH_CMD_START;
251 else
252 sc.ChangeState(SCE_SH_IDENTIFIER);
253 }
254 // 'for'|'case'|'select' needs 'in'|'do' to be highlighted later
255 else if (bashStruct_in.InList(s)) {
256 if (cmdState == BASH_CMD_START && keywordEnds)
257 cmdStateNew = BASH_CMD_WORD;
258 else
259 sc.ChangeState(SCE_SH_IDENTIFIER);
260 }
261 // disambiguate option items and file test operators
262 else if (s[0] == '-') {
263 if (cmdState != BASH_CMD_TEST)
264 sc.ChangeState(SCE_SH_IDENTIFIER);
265 }
266 // disambiguate keywords and identifiers
267 else if (cmdState != BASH_CMD_START
268 || !(keywords.InList(s) && keywordEnds)) {
9e96e16f 269 sc.ChangeState(SCE_SH_IDENTIFIER);
591d01be 270 }
9e96e16f 271 sc.SetState(SCE_SH_DEFAULT);
591d01be 272 }
9e96e16f
RD
273 break;
274 case SCE_SH_IDENTIFIER:
275 if (sc.chPrev == '\\') { // for escaped chars
276 sc.ForwardSetState(SCE_SH_DEFAULT);
277 } else if (!setWord.Contains(sc.ch)) {
278 sc.SetState(SCE_SH_DEFAULT);
591d01be 279 }
9e96e16f
RD
280 break;
281 case SCE_SH_NUMBER:
282 digit = translateBashDigit(sc.ch);
283 if (numBase == BASH_BASE_DECIMAL) {
284 if (sc.ch == '#') {
285 char s[10];
286 sc.GetCurrent(s, sizeof(s));
287 numBase = getBashNumberBase(s);
288 if (numBase != BASH_BASE_ERROR)
289 break;
290 } else if (IsADigit(sc.ch))
291 break;
292 } else if (numBase == BASH_BASE_HEX) {
293 if (IsADigit(sc.ch, 16))
294 break;
7e0c58e9 295#ifdef PEDANTIC_OCTAL
9e96e16f
RD
296 } else if (numBase == BASH_BASE_OCTAL ||
297 numBase == BASH_BASE_OCTAL_ERROR) {
298 if (digit <= 7)
299 break;
591d01be 300 if (digit <= 9) {
9e96e16f
RD
301 numBase = BASH_BASE_OCTAL_ERROR;
302 break;
591d01be 303 }
9e96e16f
RD
304#endif
305 } else if (numBase == BASH_BASE_ERROR) {
306 if (digit <= 9)
307 break;
308 } else { // DD#DDDD number style handling
309 if (digit != BASH_BASE_ERROR) {
310 if (numBase <= 36) {
311 // case-insensitive if base<=36
312 if (digit >= 36) digit -= 26;
313 }
314 if (digit < numBase)
315 break;
591d01be
RD
316 if (digit <= 9) {
317 numBase = BASH_BASE_ERROR;
9e96e16f
RD
318 break;
319 }
591d01be 320 }
9e96e16f
RD
321 }
322 // fallthrough when number is at an end or error
323 if (numBase == BASH_BASE_ERROR
7e0c58e9 324#ifdef PEDANTIC_OCTAL
9e96e16f 325 || numBase == BASH_BASE_OCTAL_ERROR
7e0c58e9 326#endif
9e96e16f
RD
327 ) {
328 sc.ChangeState(SCE_SH_ERROR);
591d01be 329 }
9e96e16f
RD
330 sc.SetState(SCE_SH_DEFAULT);
331 break;
332 case SCE_SH_COMMENTLINE:
1dcf666d
RD
333 if (sc.atLineEnd && sc.chPrev != '\\') {
334 sc.SetState(SCE_SH_DEFAULT);
591d01be 335 }
9e96e16f
RD
336 break;
337 case SCE_SH_HERE_DELIM:
591d01be
RD
338 // From Bash info:
339 // ---------------
340 // Specifier format is: <<[-]WORD
341 // Optional '-' is for removal of leading tabs from here-doc.
342 // Whitespace acceptable after <<[-] operator
343 //
344 if (HereDoc.State == 0) { // '<<' encountered
9e96e16f 345 HereDoc.Quote = sc.chNext;
591d01be
RD
346 HereDoc.Quoted = false;
347 HereDoc.DelimiterLength = 0;
348 HereDoc.Delimiter[HereDoc.DelimiterLength] = '\0';
9e96e16f
RD
349 if (sc.chNext == '\'' || sc.chNext == '\"') { // a quoted here-doc delimiter (' or ")
350 sc.Forward();
591d01be 351 HereDoc.Quoted = true;
9e96e16f
RD
352 HereDoc.State = 1;
353 } else if (!HereDoc.Indent && sc.chNext == '-') { // <<- indent case
591d01be 354 HereDoc.Indent = true;
9e96e16f 355 } else if (setHereDoc.Contains(sc.chNext)) {
591d01be 356 // an unquoted here-doc delimiter, no special handling
9e96e16f
RD
357 // TODO check what exactly bash considers part of the delim
358 HereDoc.State = 1;
359 } else if (sc.chNext == '<') { // HERE string <<<
360 sc.Forward();
361 sc.ForwardSetState(SCE_SH_DEFAULT);
362 } else if (IsASpace(sc.chNext)) {
591d01be 363 // eat whitespace
9e96e16f 364 } else if (setLeftShift.Contains(sc.chNext)) {
591d01be 365 // left shift << or <<= operator cases
9e96e16f
RD
366 sc.ChangeState(SCE_SH_OPERATOR);
367 sc.ForwardSetState(SCE_SH_DEFAULT);
591d01be
RD
368 } else {
369 // symbols terminates; deprecated zero-length delimiter
9e96e16f 370 HereDoc.State = 1;
591d01be
RD
371 }
372 } else if (HereDoc.State == 1) { // collect the delimiter
1dcf666d
RD
373 if (setHereDoc2.Contains(sc.ch) || sc.chPrev == '\\') {
374 HereDoc.Append(sc.ch);
375 } else if (HereDoc.Quoted && sc.ch == HereDoc.Quote) { // closing quote => end of delimiter
376 sc.ForwardSetState(SCE_SH_DEFAULT);
377 } else if (sc.ch == '\\') {
378 // skip escape prefix
379 } else {
380 sc.SetState(SCE_SH_DEFAULT);
591d01be 381 }
9e96e16f
RD
382 if (HereDoc.DelimiterLength >= HERE_DELIM_MAX - 1) { // force blowup
383 sc.SetState(SCE_SH_ERROR);
384 HereDoc.State = 0;
591d01be
RD
385 }
386 }
9e96e16f
RD
387 break;
388 case SCE_SH_HERE_Q:
389 // HereDoc.State == 2
390 if (sc.atLineStart) {
391 sc.SetState(SCE_SH_HERE_Q);
392 int prefixws = 0;
393 while (IsASpace(sc.ch) && !sc.atLineEnd) { // whitespace prefix
394 sc.Forward();
395 prefixws++;
396 }
397 if (prefixws > 0)
398 sc.SetState(SCE_SH_HERE_Q);
399 while (!sc.atLineEnd) {
400 sc.Forward();
401 }
402 char s[HERE_DELIM_MAX];
403 sc.GetCurrent(s, sizeof(s));
404 if (sc.LengthCurrent() == 0)
405 break;
406 if (s[strlen(s) - 1] == '\r')
407 s[strlen(s) - 1] = '\0';
408 if (strcmp(HereDoc.Delimiter, s) == 0) {
1dcf666d
RD
409 if ((prefixws == 0) || // indentation rule
410 (prefixws > 0 && HereDoc.Indent)) {
9e96e16f
RD
411 sc.SetState(SCE_SH_DEFAULT);
412 break;
591d01be
RD
413 }
414 }
415 }
9e96e16f
RD
416 break;
417 case SCE_SH_SCALAR: // variable names
418 if (!setParam.Contains(sc.ch)) {
419 if (sc.LengthCurrent() == 1) {
591d01be 420 // Special variable: $(, $_ etc.
9e96e16f 421 sc.ForwardSetState(SCE_SH_DEFAULT);
591d01be 422 } else {
9e96e16f 423 sc.SetState(SCE_SH_DEFAULT);
591d01be
RD
424 }
425 }
9e96e16f
RD
426 break;
427 case SCE_SH_STRING: // delimited styles
9e96e16f
RD
428 case SCE_SH_BACKTICKS:
429 case SCE_SH_PARAM:
430 if (sc.ch == '\\' && Quote.Up != '\\') {
431 sc.Forward();
432 } else if (sc.ch == Quote.Down) {
591d01be
RD
433 Quote.Count--;
434 if (Quote.Count == 0) {
9e96e16f 435 sc.ForwardSetState(SCE_SH_DEFAULT);
591d01be 436 }
9e96e16f 437 } else if (sc.ch == Quote.Up) {
591d01be
RD
438 Quote.Count++;
439 }
9e96e16f 440 break;
1dcf666d
RD
441 case SCE_SH_CHARACTER: // singly-quoted strings
442 if (sc.ch == Quote.Down) {
443 Quote.Count--;
444 if (Quote.Count == 0) {
445 sc.ForwardSetState(SCE_SH_DEFAULT);
446 }
447 }
448 break;
9e96e16f
RD
449 }
450
451 // Must check end of HereDoc state 1 before default state is handled
452 if (HereDoc.State == 1 && sc.atLineEnd) {
453 // Begin of here-doc (the line after the here-doc delimiter):
454 // Lexically, the here-doc starts from the next line after the >>, but the
455 // first line of here-doc seem to follow the style of the last EOL sequence
456 HereDoc.State = 2;
457 if (HereDoc.Quoted) {
458 if (sc.state == SCE_SH_HERE_DELIM) {
459 // Missing quote at end of string! We are stricter than bash.
460 // Colour here-doc anyway while marking this bit as an error.
461 sc.ChangeState(SCE_SH_ERROR);
462 }
463 // HereDoc.Quote always == '\''
591d01be 464 }
9e96e16f 465 sc.SetState(SCE_SH_HERE_Q);
591d01be 466 }
9e96e16f 467
1dcf666d
RD
468 // update cmdState about the current command segment
469 if (stylePrev != SCE_SH_DEFAULT && sc.state == SCE_SH_DEFAULT) {
470 cmdState = cmdStateNew;
471 }
9e96e16f
RD
472 // Determine if a new state should be entered.
473 if (sc.state == SCE_SH_DEFAULT) {
1dcf666d
RD
474 if (sc.ch == '\\') {
475 // Bash can escape any non-newline as a literal
9e96e16f 476 sc.SetState(SCE_SH_IDENTIFIER);
1dcf666d
RD
477 if (sc.chNext == '\r' || sc.chNext == '\n')
478 sc.SetState(SCE_SH_OPERATOR);
9e96e16f
RD
479 } else if (IsADigit(sc.ch)) {
480 sc.SetState(SCE_SH_NUMBER);
481 numBase = BASH_BASE_DECIMAL;
482 if (sc.ch == '0') { // hex,octal
483 if (sc.chNext == 'x' || sc.chNext == 'X') {
484 numBase = BASH_BASE_HEX;
485 sc.Forward();
486 } else if (IsADigit(sc.chNext)) {
487#ifdef PEDANTIC_OCTAL
488 numBase = BASH_BASE_OCTAL;
489#else
490 numBase = BASH_BASE_HEX;
491#endif
492 }
493 }
494 } else if (setWordStart.Contains(sc.ch)) {
495 sc.SetState(SCE_SH_WORD);
496 } else if (sc.ch == '#') {
497 sc.SetState(SCE_SH_COMMENTLINE);
498 } else if (sc.ch == '\"') {
499 sc.SetState(SCE_SH_STRING);
500 Quote.Start(sc.ch);
501 } else if (sc.ch == '\'') {
502 sc.SetState(SCE_SH_CHARACTER);
503 Quote.Start(sc.ch);
504 } else if (sc.ch == '`') {
505 sc.SetState(SCE_SH_BACKTICKS);
506 Quote.Start(sc.ch);
507 } else if (sc.ch == '$') {
1dcf666d
RD
508 if (sc.Match("$((")) {
509 sc.SetState(SCE_SH_OPERATOR); // handle '((' later
510 continue;
511 }
9e96e16f
RD
512 sc.SetState(SCE_SH_SCALAR);
513 sc.Forward();
514 if (sc.ch == '{') {
515 sc.ChangeState(SCE_SH_PARAM);
516 } else if (sc.ch == '\'') {
1dcf666d 517 sc.ChangeState(SCE_SH_STRING);
9e96e16f
RD
518 } else if (sc.ch == '"') {
519 sc.ChangeState(SCE_SH_STRING);
520 } else if (sc.ch == '(' || sc.ch == '`') {
521 sc.ChangeState(SCE_SH_BACKTICKS);
9e96e16f
RD
522 } else {
523 continue; // scalar has no delimiter pair
524 }
525 // fallthrough, open delim for $[{'"(`]
526 Quote.Start(sc.ch);
527 } else if (sc.Match('<', '<')) {
528 sc.SetState(SCE_SH_HERE_DELIM);
529 HereDoc.State = 0;
530 HereDoc.Indent = false;
531 } else if (sc.ch == '-' && // one-char file test operators
532 setSingleCharOp.Contains(sc.chNext) &&
533 !setWord.Contains(sc.GetRelative(2)) &&
534 IsASpace(sc.chPrev)) {
535 sc.SetState(SCE_SH_WORD);
536 sc.Forward();
537 } else if (setBashOperator.Contains(sc.ch)) {
1dcf666d
RD
538 char s[10];
539 bool isCmdDelim = false;
9e96e16f 540 sc.SetState(SCE_SH_OPERATOR);
1dcf666d
RD
541 // handle opening delimiters for test/arithmetic expressions - ((,[[,[
542 if (cmdState == BASH_CMD_START
543 || cmdState == BASH_CMD_BODY) {
544 if (sc.Match('(', '(')) {
545 cmdState = BASH_CMD_ARITH;
546 sc.Forward();
547 } else if (sc.Match('[', '[') && IsASpace(sc.GetRelative(2))) {
548 cmdState = BASH_CMD_TEST;
549 testExprType = 1;
550 sc.Forward();
551 } else if (sc.ch == '[' && IsASpace(sc.chNext)) {
552 cmdState = BASH_CMD_TEST;
553 testExprType = 2;
554 }
555 }
556 // special state -- for ((x;y;z)) in ... looping
557 if (cmdState == BASH_CMD_WORD && sc.Match('(', '(')) {
558 cmdState = BASH_CMD_ARITH;
559 sc.Forward();
560 continue;
561 }
562 // handle command delimiters in command START|BODY|WORD state, also TEST if 'test'
563 if (cmdState == BASH_CMD_START
564 || cmdState == BASH_CMD_BODY
565 || cmdState == BASH_CMD_WORD
566 || (cmdState == BASH_CMD_TEST && testExprType == 0)) {
567 s[0] = static_cast<char>(sc.ch);
568 if (setBashOperator.Contains(sc.chNext)) {
569 s[1] = static_cast<char>(sc.chNext);
570 s[2] = '\0';
571 isCmdDelim = cmdDelimiter.InList(s);
572 if (isCmdDelim)
573 sc.Forward();
574 }
575 if (!isCmdDelim) {
576 s[1] = '\0';
577 isCmdDelim = cmdDelimiter.InList(s);
578 }
579 if (isCmdDelim) {
580 cmdState = BASH_CMD_DELIM;
581 continue;
582 }
583 }
584 // handle closing delimiters for test/arithmetic expressions - )),]],]
585 if (cmdState == BASH_CMD_ARITH && sc.Match(')', ')')) {
586 cmdState = BASH_CMD_BODY;
587 sc.Forward();
588 } else if (cmdState == BASH_CMD_TEST && IsASpace(sc.chPrev)) {
589 if (sc.Match(']', ']') && testExprType == 1) {
590 sc.Forward();
591 cmdState = BASH_CMD_BODY;
592 } else if (sc.ch == ']' && testExprType == 2) {
593 cmdState = BASH_CMD_BODY;
594 }
595 }
9e96e16f 596 }
1dcf666d 597 }// sc.state
591d01be 598 }
9e96e16f 599 sc.Complete();
591d01be
RD
600}
601
1e9bafca
RD
602static bool IsCommentLine(int line, Accessor &styler) {
603 int pos = styler.LineStart(line);
604 int eol_pos = styler.LineStart(line + 1) - 1;
605 for (int i = pos; i < eol_pos; i++) {
606 char ch = styler[i];
607 if (ch == '#')
608 return true;
609 else if (ch != ' ' && ch != '\t')
610 return false;
611 }
612 return false;
613}
614
591d01be 615static void FoldBashDoc(unsigned int startPos, int length, int, WordList *[],
9e96e16f 616 Accessor &styler) {
591d01be
RD
617 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
618 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
619 unsigned int endPos = startPos + length;
620 int visibleChars = 0;
621 int lineCurrent = styler.GetLine(startPos);
622 int levelPrev = styler.LevelAt(lineCurrent) & SC_FOLDLEVELNUMBERMASK;
623 int levelCurrent = levelPrev;
624 char chNext = styler[startPos];
625 int styleNext = styler.StyleAt(startPos);
626 for (unsigned int i = startPos; i < endPos; i++) {
627 char ch = chNext;
628 chNext = styler.SafeGetCharAt(i + 1);
629 int style = styleNext;
630 styleNext = styler.StyleAt(i + 1);
631 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
9e96e16f 632 // Comment folding
1e9bafca 633 if (foldComment && atEOL && IsCommentLine(lineCurrent, styler))
9e96e16f
RD
634 {
635 if (!IsCommentLine(lineCurrent - 1, styler)
636 && IsCommentLine(lineCurrent + 1, styler))
637 levelCurrent++;
638 else if (IsCommentLine(lineCurrent - 1, styler)
639 && !IsCommentLine(lineCurrent + 1, styler))
640 levelCurrent--;
641 }
b8193d80 642 if (style == SCE_SH_OPERATOR) {
591d01be
RD
643 if (ch == '{') {
644 levelCurrent++;
645 } else if (ch == '}') {
646 levelCurrent--;
647 }
648 }
1dcf666d
RD
649 // Here Document folding
650 if (style == SCE_SH_HERE_DELIM) {
651 if (ch == '<' && chNext == '<') {
652 levelCurrent++;
653 }
654 } else if (style == SCE_SH_HERE_Q && styler.StyleAt(i+1) == SCE_PL_DEFAULT) {
655 levelCurrent--;
656 }
591d01be
RD
657 if (atEOL) {
658 int lev = levelPrev;
659 if (visibleChars == 0 && foldCompact)
660 lev |= SC_FOLDLEVELWHITEFLAG;
661 if ((levelCurrent > levelPrev) && (visibleChars > 0))
662 lev |= SC_FOLDLEVELHEADERFLAG;
663 if (lev != styler.LevelAt(lineCurrent)) {
664 styler.SetLevel(lineCurrent, lev);
665 }
666 lineCurrent++;
667 levelPrev = levelCurrent;
668 visibleChars = 0;
669 }
670 if (!isspacechar(ch))
671 visibleChars++;
672 }
673 // Fill in the real level of the next line, keeping the current flags as they will be filled in later
674 int flagsNext = styler.LevelAt(lineCurrent) & ~SC_FOLDLEVELNUMBERMASK;
675 styler.SetLevel(lineCurrent, levelPrev | flagsNext);
676}
677
678static const char * const bashWordListDesc[] = {
679 "Keywords",
680 0
681};
682
683LexerModule lmBash(SCLEX_BASH, ColouriseBashDoc, "bash", FoldBashDoc, bashWordListDesc);