1 //-*- coding: utf-8 -*-
2 // Scintilla source code edit control
4 ** Lexer for SQL, including PL/SQL and SQL*Plus.
5 ** Improved by Jérôme LAFORGE <jerome.laforge_AT_gmail_DOT_com> from 2010 to 2012.
7 // Copyright 1998-2012 by Neil Hodgson <neilh@scintilla.org>
8 // The License.txt file describes the conditions under which this software may be distributed.
23 #include "Scintilla.h"
27 #include "LexAccessor.h"
29 #include "StyleContext.h"
30 #include "CharacterSet.h"
31 #include "LexerModule.h"
32 #include "OptionSet.h"
33 #include "SparseState.h"
36 using namespace Scintilla
;
39 static inline bool IsAWordChar(int ch
, bool sqlAllowDottedWord
) {
40 if (!sqlAllowDottedWord
)
41 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_');
43 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_' || ch
== '.');
46 static inline bool IsAWordStart(int ch
) {
47 return (ch
< 0x80) && (isalpha(ch
) || ch
== '_');
50 static inline bool IsADoxygenChar(int ch
) {
51 return (islower(ch
) || ch
== '$' || ch
== '@' ||
52 ch
== '\\' || ch
== '&' || ch
== '<' ||
53 ch
== '>' || ch
== '#' || ch
== '{' ||
54 ch
== '}' || ch
== '[' || ch
== ']');
57 static inline bool IsANumberChar(int ch
) {
58 // Not exactly following number definition (several dots are seen as OK, etc.)
59 // but probably enough in most cases.
61 (isdigit(ch
) || toupper(ch
) == 'E' ||
62 ch
== '.' || ch
== '-' || ch
== '+');
68 void Set(int lineNumber
, unsigned short int sqlStatesLine
) {
69 sqlStatement
.Set(lineNumber
, sqlStatesLine
);
72 unsigned short int IgnoreWhen (unsigned short int sqlStatesLine
, bool enable
) {
74 sqlStatesLine
|= MASK_IGNORE_WHEN
;
76 sqlStatesLine
&= ~MASK_IGNORE_WHEN
;
81 unsigned short int IntoCondition (unsigned short int sqlStatesLine
, bool enable
) {
83 sqlStatesLine
|= MASK_INTO_CONDITION
;
85 sqlStatesLine
&= ~MASK_INTO_CONDITION
;
90 unsigned short int IntoExceptionBlock (unsigned short int sqlStatesLine
, bool enable
) {
92 sqlStatesLine
|= MASK_INTO_EXCEPTION
;
94 sqlStatesLine
&= ~MASK_INTO_EXCEPTION
;
99 unsigned short int IntoDeclareBlock (unsigned short int sqlStatesLine
, bool enable
) {
101 sqlStatesLine
|= MASK_INTO_DECLARE
;
103 sqlStatesLine
&= ~MASK_INTO_DECLARE
;
105 return sqlStatesLine
;
108 unsigned short int IntoMergeStatement (unsigned short int sqlStatesLine
, bool enable
) {
110 sqlStatesLine
|= MASK_MERGE_STATEMENT
;
112 sqlStatesLine
&= ~MASK_MERGE_STATEMENT
;
114 return sqlStatesLine
;
117 unsigned short int CaseMergeWithoutWhenFound (unsigned short int sqlStatesLine
, bool found
) {
119 sqlStatesLine
|= MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
;
121 sqlStatesLine
&= ~MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
;
123 return sqlStatesLine
;
125 unsigned short int IntoSelectStatementOrAssignment (unsigned short int sqlStatesLine
, bool found
) {
127 sqlStatesLine
|= MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
;
129 sqlStatesLine
&= ~MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
;
130 return sqlStatesLine
;
133 unsigned short int BeginCaseBlock (unsigned short int sqlStatesLine
) {
134 if ((sqlStatesLine
& MASK_NESTED_CASES
) < MASK_NESTED_CASES
) {
137 return sqlStatesLine
;
140 unsigned short int EndCaseBlock (unsigned short int sqlStatesLine
) {
141 if ((sqlStatesLine
& MASK_NESTED_CASES
) > 0) {
144 return sqlStatesLine
;
147 bool IsIgnoreWhen (unsigned short int sqlStatesLine
) {
148 return (sqlStatesLine
& MASK_IGNORE_WHEN
) != 0;
151 bool IsIntoCondition (unsigned short int sqlStatesLine
) {
152 return (sqlStatesLine
& MASK_INTO_CONDITION
) != 0;
155 bool IsIntoCaseBlock (unsigned short int sqlStatesLine
) {
156 return (sqlStatesLine
& MASK_NESTED_CASES
) != 0;
159 bool IsIntoExceptionBlock (unsigned short int sqlStatesLine
) {
160 return (sqlStatesLine
& MASK_INTO_EXCEPTION
) != 0;
162 bool IsIntoSelectStatementOrAssignment (unsigned short int sqlStatesLine
) {
163 return (sqlStatesLine
& MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
) != 0;
165 bool IsCaseMergeWithoutWhenFound (unsigned short int sqlStatesLine
) {
166 return (sqlStatesLine
& MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
) != 0;
169 bool IsIntoDeclareBlock (unsigned short int sqlStatesLine
) {
170 return (sqlStatesLine
& MASK_INTO_DECLARE
) != 0;
173 bool IsIntoMergeStatement (unsigned short int sqlStatesLine
) {
174 return (sqlStatesLine
& MASK_MERGE_STATEMENT
) != 0;
177 unsigned short int ForLine(int lineNumber
) {
178 return sqlStatement
.ValueAt(lineNumber
);
184 SparseState
<unsigned short int> sqlStatement
;
186 MASK_NESTED_CASES
= 0x01FF,
187 MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
= 0x0200,
188 MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
= 0x0400,
189 MASK_MERGE_STATEMENT
= 0x0800,
190 MASK_INTO_DECLARE
= 0x1000,
191 MASK_INTO_EXCEPTION
= 0x2000,
192 MASK_INTO_CONDITION
= 0x4000,
193 MASK_IGNORE_WHEN
= 0x8000
197 // Options used for LexerSQL
204 bool sqlBackticksIdentifier
;
205 bool sqlNumbersignComment
;
206 bool sqlBackslashEscapes
;
207 bool sqlAllowDottedWord
;
213 foldOnlyBegin
= false;
214 sqlBackticksIdentifier
= false;
215 sqlNumbersignComment
= false;
216 sqlBackslashEscapes
= false;
217 sqlAllowDottedWord
= false;
221 static const char * const sqlWordListDesc
[] = {
233 struct OptionSetSQL
: public OptionSet
<OptionsSQL
> {
235 DefineProperty("fold", &OptionsSQL::fold
);
237 DefineProperty("fold.sql.at.else", &OptionsSQL::foldAtElse
,
238 "This option enables SQL folding on a \"ELSE\" and \"ELSIF\" line of an IF statement.");
240 DefineProperty("fold.comment", &OptionsSQL::foldComment
);
242 DefineProperty("fold.compact", &OptionsSQL::foldCompact
);
244 DefineProperty("fold.sql.only.begin", &OptionsSQL::foldOnlyBegin
);
246 DefineProperty("lexer.sql.backticks.identifier", &OptionsSQL::sqlBackticksIdentifier
);
248 DefineProperty("lexer.sql.numbersign.comment", &OptionsSQL::sqlNumbersignComment
,
249 "If \"lexer.sql.numbersign.comment\" property is set to 0 a line beginning with '#' will not be a comment.");
251 DefineProperty("sql.backslash.escapes", &OptionsSQL::sqlBackslashEscapes
,
252 "Enables backslash as an escape character in SQL.");
254 DefineProperty("lexer.sql.allow.dotted.word", &OptionsSQL::sqlAllowDottedWord
,
255 "Set to 1 to colourise recognized words with dots "
256 "(recommended for Oracle PL/SQL objects).");
258 DefineWordListSets(sqlWordListDesc
);
262 class LexerSQL
: public ILexer
{
266 virtual ~LexerSQL() {}
268 int SCI_METHOD
Version () const {
272 void SCI_METHOD
Release() {
276 const char * SCI_METHOD
PropertyNames() {
277 return osSQL
.PropertyNames();
280 int SCI_METHOD
PropertyType(const char *name
) {
281 return osSQL
.PropertyType(name
);
284 const char * SCI_METHOD
DescribeProperty(const char *name
) {
285 return osSQL
.DescribeProperty(name
);
288 int SCI_METHOD
PropertySet(const char *key
, const char *val
) {
289 if (osSQL
.PropertySet(&options
, key
, val
)) {
295 const char * SCI_METHOD
DescribeWordListSets() {
296 return osSQL
.DescribeWordListSets();
299 int SCI_METHOD
WordListSet(int n
, const char *wl
);
300 void SCI_METHOD
Lex (unsigned int startPos
, int lengthDoc
, int initStyle
, IDocument
*pAccess
);
301 void SCI_METHOD
Fold(unsigned int startPos
, int lengthDoc
, int initStyle
, IDocument
*pAccess
);
303 void * SCI_METHOD
PrivateCall(int, void *) {
307 static ILexer
*LexerFactorySQL() {
308 return new LexerSQL();
311 bool IsStreamCommentStyle(int style
) {
312 return style
== SCE_SQL_COMMENT
||
313 style
== SCE_SQL_COMMENTDOC
||
314 style
== SCE_SQL_COMMENTDOCKEYWORD
||
315 style
== SCE_SQL_COMMENTDOCKEYWORDERROR
;
318 bool IsCommentStyle (int style
) {
320 case SCE_SQL_COMMENT
:
321 case SCE_SQL_COMMENTDOC
:
322 case SCE_SQL_COMMENTLINE
:
323 case SCE_SQL_COMMENTLINEDOC
:
324 case SCE_SQL_COMMENTDOCKEYWORD
:
325 case SCE_SQL_COMMENTDOCKEYWORDERROR
:
332 bool IsCommentLine (int line
, LexAccessor
&styler
) {
333 int pos
= styler
.LineStart(line
);
334 int eol_pos
= styler
.LineStart(line
+ 1) - 1;
335 for (int i
= pos
; i
+ 1 < eol_pos
; i
++) {
336 int style
= styler
.StyleAt(i
);
337 // MySQL needs -- comments to be followed by space or control char
338 if (style
== SCE_SQL_COMMENTLINE
&& styler
.Match(i
, "--"))
340 else if (!IsASpaceOrTab(styler
[i
]))
360 int SCI_METHOD
LexerSQL::WordListSet(int n
, const char *wl
) {
361 WordList
*wordListN
= 0;
364 wordListN
= &keywords1
;
367 wordListN
= &keywords2
;
370 wordListN
= &kw_pldoc
;
373 wordListN
= &kw_sqlplus
;
376 wordListN
= &kw_user1
;
379 wordListN
= &kw_user2
;
382 wordListN
= &kw_user3
;
385 wordListN
= &kw_user4
;
387 int firstModification
= -1;
391 if (*wordListN
!= wlNew
) {
393 firstModification
= 0;
396 return firstModification
;
399 void SCI_METHOD
LexerSQL::Lex(unsigned int startPos
, int length
, int initStyle
, IDocument
*pAccess
) {
400 LexAccessor
styler(pAccess
);
401 StyleContext
sc(startPos
, length
, initStyle
, styler
);
402 int styleBeforeDCKeyword
= SCE_SQL_DEFAULT
;
404 for (; sc
.More(); sc
.Forward(), offset
++) {
405 // Determine if the current state should terminate.
407 case SCE_SQL_OPERATOR
:
408 sc
.SetState(SCE_SQL_DEFAULT
);
411 // We stop the number definition on non-numerical non-dot non-eE non-sign char
412 if (!IsANumberChar(sc
.ch
)) {
413 sc
.SetState(SCE_SQL_DEFAULT
);
416 case SCE_SQL_IDENTIFIER
:
417 if (!IsAWordChar(sc
.ch
, options
.sqlAllowDottedWord
)) {
418 int nextState
= SCE_SQL_DEFAULT
;
420 sc
.GetCurrentLowered(s
, sizeof(s
));
421 if (keywords1
.InList(s
)) {
422 sc
.ChangeState(SCE_SQL_WORD
);
423 } else if (keywords2
.InList(s
)) {
424 sc
.ChangeState(SCE_SQL_WORD2
);
425 } else if (kw_sqlplus
.InListAbbreviated(s
, '~')) {
426 sc
.ChangeState(SCE_SQL_SQLPLUS
);
427 if (strncmp(s
, "rem", 3) == 0) {
428 nextState
= SCE_SQL_SQLPLUS_COMMENT
;
429 } else if (strncmp(s
, "pro", 3) == 0) {
430 nextState
= SCE_SQL_SQLPLUS_PROMPT
;
432 } else if (kw_user1
.InList(s
)) {
433 sc
.ChangeState(SCE_SQL_USER1
);
434 } else if (kw_user2
.InList(s
)) {
435 sc
.ChangeState(SCE_SQL_USER2
);
436 } else if (kw_user3
.InList(s
)) {
437 sc
.ChangeState(SCE_SQL_USER3
);
438 } else if (kw_user4
.InList(s
)) {
439 sc
.ChangeState(SCE_SQL_USER4
);
441 sc
.SetState(nextState
);
444 case SCE_SQL_QUOTEDIDENTIFIER
:
446 if (sc
.chNext
== 0x60) {
447 sc
.Forward(); // Ignore it
449 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
453 case SCE_SQL_COMMENT
:
454 if (sc
.Match('*', '/')) {
456 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
459 case SCE_SQL_COMMENTDOC
:
460 if (sc
.Match('*', '/')) {
462 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
463 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // Doxygen support
464 // Verify that we have the conditions to mark a comment-doc-keyword
465 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '*') && (!IsASpace(sc
.chNext
))) {
466 styleBeforeDCKeyword
= SCE_SQL_COMMENTDOC
;
467 sc
.SetState(SCE_SQL_COMMENTDOCKEYWORD
);
471 case SCE_SQL_COMMENTLINE
:
472 case SCE_SQL_COMMENTLINEDOC
:
473 case SCE_SQL_SQLPLUS_COMMENT
:
474 case SCE_SQL_SQLPLUS_PROMPT
:
475 if (sc
.atLineStart
) {
476 sc
.SetState(SCE_SQL_DEFAULT
);
479 case SCE_SQL_COMMENTDOCKEYWORD
:
480 if ((styleBeforeDCKeyword
== SCE_SQL_COMMENTDOC
) && sc
.Match('*', '/')) {
481 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
483 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
484 } else if (!IsADoxygenChar(sc
.ch
)) {
486 sc
.GetCurrentLowered(s
, sizeof(s
));
487 if (!isspace(sc
.ch
) || !kw_pldoc
.InList(s
+ 1)) {
488 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
490 sc
.SetState(styleBeforeDCKeyword
);
493 case SCE_SQL_CHARACTER
:
494 if (options
.sqlBackslashEscapes
&& sc
.ch
== '\\') {
496 } else if (sc
.ch
== '\'') {
497 if (sc
.chNext
== '\"') {
500 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
508 } else if (sc
.ch
== '\"') {
509 if (sc
.chNext
== '\"') {
512 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
518 // Determine if a new state should be entered.
519 if (sc
.state
== SCE_SQL_DEFAULT
) {
520 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
))) {
521 sc
.SetState(SCE_SQL_NUMBER
);
522 } else if (IsAWordStart(sc
.ch
)) {
523 sc
.SetState(SCE_SQL_IDENTIFIER
);
524 } else if (sc
.ch
== 0x60 && options
.sqlBackticksIdentifier
) {
525 sc
.SetState(SCE_SQL_QUOTEDIDENTIFIER
);
526 } else if (sc
.Match('/', '*')) {
527 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Doxygen doc. style
528 sc
.SetState(SCE_SQL_COMMENTDOC
);
530 sc
.SetState(SCE_SQL_COMMENT
);
532 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
533 } else if (sc
.Match('-', '-')) {
534 // MySQL requires a space or control char after --
535 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
536 // Perhaps we should enforce that with proper property:
537 //~ } else if (sc.Match("-- ")) {
538 sc
.SetState(SCE_SQL_COMMENTLINE
);
539 } else if (sc
.ch
== '#' && options
.sqlNumbersignComment
) {
540 sc
.SetState(SCE_SQL_COMMENTLINEDOC
);
541 } else if (sc
.ch
== '\'') {
542 sc
.SetState(SCE_SQL_CHARACTER
);
543 } else if (sc
.ch
== '\"') {
544 sc
.SetState(SCE_SQL_STRING
);
545 } else if (isoperator(static_cast<char>(sc
.ch
))) {
546 sc
.SetState(SCE_SQL_OPERATOR
);
553 void SCI_METHOD
LexerSQL::Fold(unsigned int startPos
, int length
, int initStyle
, IDocument
*pAccess
) {
556 LexAccessor
styler(pAccess
);
557 unsigned int endPos
= startPos
+ length
;
558 int visibleChars
= 0;
559 int lineCurrent
= styler
.GetLine(startPos
);
560 int levelCurrent
= SC_FOLDLEVELBASE
;
562 if (lineCurrent
> 0) {
563 // Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
565 startPos
= styler
.LineStart(lineCurrent
);
568 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
570 int levelNext
= levelCurrent
;
571 char chNext
= styler
[startPos
];
572 int styleNext
= styler
.StyleAt(startPos
);
573 int style
= initStyle
;
574 bool endFound
= false;
575 bool isUnfoldingIgnored
= false;
576 // this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
577 // eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
578 bool statementFound
= false;
579 unsigned short int sqlStatesCurrentLine
= 0;
580 if (!options
.foldOnlyBegin
) {
581 sqlStatesCurrentLine
= sqlStates
.ForLine(lineCurrent
);
583 for (unsigned int i
= startPos
; i
< endPos
; i
++) {
585 chNext
= styler
.SafeGetCharAt(i
+ 1);
586 int stylePrev
= style
;
588 styleNext
= styler
.StyleAt(i
+ 1);
589 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
590 if (atEOL
|| (!IsCommentStyle(style
) && ch
== ';')) {
592 //Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
593 sqlStatesCurrentLine
= sqlStates
.IntoExceptionBlock(sqlStatesCurrentLine
, false);
595 // set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
597 isUnfoldingIgnored
= false;
599 if ((!IsCommentStyle(style
) && ch
== ';')) {
600 if (sqlStates
.IsIntoMergeStatement(sqlStatesCurrentLine
)) {
601 // This is the end of "MERGE" statement.
602 if (!sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
))
604 sqlStatesCurrentLine
= sqlStates
.IntoMergeStatement(sqlStatesCurrentLine
, false);
607 if (sqlStates
.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine
))
608 sqlStatesCurrentLine
= sqlStates
.IntoSelectStatementOrAssignment(sqlStatesCurrentLine
, false);
610 if (ch
== ':' && chNext
== '=' && !IsCommentStyle(style
))
611 sqlStatesCurrentLine
= sqlStates
.IntoSelectStatementOrAssignment(sqlStatesCurrentLine
, true);
613 if (options
.foldComment
&& IsStreamCommentStyle(style
)) {
614 if (!IsStreamCommentStyle(stylePrev
)) {
616 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
617 // Comments don't end at end of line and the next character may be unstyled.
621 if (options
.foldComment
&& (style
== SCE_SQL_COMMENTLINE
)) {
622 // MySQL needs -- comments to be followed by space or control char
623 if ((ch
== '-') && (chNext
== '-')) {
624 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
625 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
626 if (chNext2
== '{' || chNext3
== '{') {
628 } else if (chNext2
== '}' || chNext3
== '}') {
633 // Fold block of single-line comments (i.e. '--').
634 if (options
.foldComment
&& atEOL
&& IsCommentLine(lineCurrent
, styler
)) {
635 if (!IsCommentLine(lineCurrent
- 1, styler
) && IsCommentLine(lineCurrent
+ 1, styler
))
637 else if (IsCommentLine(lineCurrent
- 1, styler
) && !IsCommentLine(lineCurrent
+ 1, styler
))
640 if (style
== SCE_SQL_OPERATOR
) {
642 if (levelCurrent
> levelNext
)
645 } else if (ch
== ')') {
647 } else if ((!options
.foldOnlyBegin
) && ch
== ';') {
648 sqlStatesCurrentLine
= sqlStates
.IgnoreWhen(sqlStatesCurrentLine
, false);
651 // If new keyword (cannot trigger on elseif or nullif, does less tests)
652 if (style
== SCE_SQL_WORD
&& stylePrev
!= SCE_SQL_WORD
) {
653 const int MAX_KW_LEN
= 9; // Maximum length of folding keywords
654 char s
[MAX_KW_LEN
+ 2];
656 for (; j
< MAX_KW_LEN
+ 1; j
++) {
657 if (!iswordchar(styler
[i
+ j
])) {
660 s
[j
] = static_cast<char>(tolower(styler
[i
+ j
]));
662 if (j
== MAX_KW_LEN
+ 1) {
663 // Keyword too long, don't test it
668 if (!options
.foldOnlyBegin
&&
669 strcmp(s
, "select") == 0) {
670 sqlStatesCurrentLine
= sqlStates
.IntoSelectStatementOrAssignment(sqlStatesCurrentLine
, true);
671 } else if (strcmp(s
, "if") == 0) {
674 if (options
.foldOnlyBegin
&& !isUnfoldingIgnored
) {
675 // this end isn't for begin block, but for if block ("end if;")
676 // so ignore previous "end" by increment levelNext.
680 if (!options
.foldOnlyBegin
)
681 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, true);
682 if (levelCurrent
> levelNext
) {
683 // doesn't include this line into the folding block
684 // because doesn't hide IF (eg "END; IF")
685 levelCurrent
= levelNext
;
688 } else if (!options
.foldOnlyBegin
&&
689 strcmp(s
, "then") == 0 &&
690 sqlStates
.IsIntoCondition(sqlStatesCurrentLine
)) {
691 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, false);
692 if (!options
.foldOnlyBegin
) {
693 if (levelCurrent
> levelNext
) {
694 levelCurrent
= levelNext
;
699 statementFound
= true;
700 } else if (levelCurrent
> levelNext
) {
701 // doesn't include this line into the folding block
702 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
703 levelCurrent
= levelNext
;
705 } else if (strcmp(s
, "loop") == 0 ||
706 strcmp(s
, "case") == 0) {
709 if (options
.foldOnlyBegin
&& !isUnfoldingIgnored
) {
710 // this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
711 // so ignore previous "end" by increment levelNext.
714 if ((!options
.foldOnlyBegin
) && strcmp(s
, "case") == 0) {
715 sqlStatesCurrentLine
= sqlStates
.EndCaseBlock(sqlStatesCurrentLine
);
716 if (!sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
))
717 levelNext
--; //again for the "end case;" and block when
719 } else if (!options
.foldOnlyBegin
) {
720 if (strcmp(s
, "case") == 0) {
721 sqlStatesCurrentLine
= sqlStates
.BeginCaseBlock(sqlStatesCurrentLine
);
722 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, true);
725 if (levelCurrent
> levelNext
)
726 levelCurrent
= levelNext
;
731 statementFound
= true;
732 } else if (levelCurrent
> levelNext
) {
733 // doesn't include this line into the folding block
734 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
735 levelCurrent
= levelNext
;
737 } else if ((!options
.foldOnlyBegin
) && (
738 // folding for ELSE and ELSIF block only if foldAtElse is set
739 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
740 options
.foldAtElse
&& !statementFound
) && strcmp(s
, "elsif") == 0) {
741 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, true);
744 } else if ((!options
.foldOnlyBegin
) && (
745 // folding for ELSE and ELSIF block only if foldAtElse is set
746 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
747 options
.foldAtElse
&& !statementFound
) && strcmp(s
, "else") == 0) {
748 // prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
749 statementFound
= true;
750 if (sqlStates
.IsIntoCaseBlock(sqlStatesCurrentLine
) && sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
)) {
751 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, false);
754 // we are in same case "} ELSE {" in C language
757 } else if (strcmp(s
, "begin") == 0) {
759 sqlStatesCurrentLine
= sqlStates
.IntoDeclareBlock(sqlStatesCurrentLine
, false);
760 } else if ((strcmp(s
, "end") == 0) ||
761 // SQL Anywhere permits IF ... ELSE ... ENDIF
762 // will only be active if "endif" appears in the
764 (strcmp(s
, "endif") == 0)) {
767 if (sqlStates
.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine
) && !sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
))
769 if (levelNext
< SC_FOLDLEVELBASE
) {
770 levelNext
= SC_FOLDLEVELBASE
;
771 isUnfoldingIgnored
= true;
773 } else if ((!options
.foldOnlyBegin
) &&
774 strcmp(s
, "when") == 0 &&
775 !sqlStates
.IsIgnoreWhen(sqlStatesCurrentLine
) &&
776 !sqlStates
.IsIntoExceptionBlock(sqlStatesCurrentLine
) && (
777 sqlStates
.IsIntoCaseBlock(sqlStatesCurrentLine
) ||
778 sqlStates
.IsIntoMergeStatement(sqlStatesCurrentLine
)
781 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, true);
783 // Don't foldind when CASE and WHEN are on the same line (with flag statementFound) (eg. "CASE selector WHEN expression1 THEN sequence_of_statements1;\n")
784 // and same way for MERGE statement.
785 if (!statementFound
) {
786 if (!sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
)) {
790 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, false);
792 } else if ((!options
.foldOnlyBegin
) && strcmp(s
, "exit") == 0) {
793 sqlStatesCurrentLine
= sqlStates
.IgnoreWhen(sqlStatesCurrentLine
, true);
794 } else if ((!options
.foldOnlyBegin
) && !sqlStates
.IsIntoDeclareBlock(sqlStatesCurrentLine
) && strcmp(s
, "exception") == 0) {
795 sqlStatesCurrentLine
= sqlStates
.IntoExceptionBlock(sqlStatesCurrentLine
, true);
796 } else if ((!options
.foldOnlyBegin
) &&
797 (strcmp(s
, "declare") == 0 ||
798 strcmp(s
, "function") == 0 ||
799 strcmp(s
, "procedure") == 0 ||
800 strcmp(s
, "package") == 0)) {
801 sqlStatesCurrentLine
= sqlStates
.IntoDeclareBlock(sqlStatesCurrentLine
, true);
802 } else if ((!options
.foldOnlyBegin
) &&
803 strcmp(s
, "merge") == 0) {
804 sqlStatesCurrentLine
= sqlStates
.IntoMergeStatement(sqlStatesCurrentLine
, true);
805 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, true);
807 statementFound
= true;
811 int levelUse
= levelCurrent
;
812 int lev
= levelUse
| levelNext
<< 16;
813 if (visibleChars
== 0 && options
.foldCompact
)
814 lev
|= SC_FOLDLEVELWHITEFLAG
;
815 if (levelUse
< levelNext
)
816 lev
|= SC_FOLDLEVELHEADERFLAG
;
817 if (lev
!= styler
.LevelAt(lineCurrent
)) {
818 styler
.SetLevel(lineCurrent
, lev
);
821 levelCurrent
= levelNext
;
823 statementFound
= false;
824 if (!options
.foldOnlyBegin
)
825 sqlStates
.Set(lineCurrent
, sqlStatesCurrentLine
);
827 if (!isspacechar(ch
)) {
833 LexerModule
lmSQL(SCLEX_SQL
, LexerSQL::LexerFactorySQL
, "sql", sqlWordListDesc
);