PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/wx.mod/wxscintilla.mod/src/scintilla/src/LexBash.cxx

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