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"
34 #include "DefaultLexer.h"
36 using namespace Scintilla
;
38 static inline bool IsAWordChar(int ch
, bool sqlAllowDottedWord
) {
39 if (!sqlAllowDottedWord
)
40 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_');
42 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_' || ch
== '.');
45 static inline bool IsAWordStart(int ch
) {
46 return (ch
< 0x80) && (isalpha(ch
) || ch
== '_');
49 static inline bool IsADoxygenChar(int ch
) {
50 return (islower(ch
) || ch
== '$' || ch
== '@' ||
51 ch
== '\\' || ch
== '&' || ch
== '<' ||
52 ch
== '>' || ch
== '#' || ch
== '{' ||
53 ch
== '}' || ch
== '[' || ch
== ']');
56 static inline bool IsANumberChar(int ch
, int chPrev
) {
57 // Not exactly following number definition (several dots are seen as OK, etc.)
58 // but probably enough in most cases.
60 (isdigit(ch
) || toupper(ch
) == 'E' ||
61 ch
== '.' || ((ch
== '-' || ch
== '+') && chPrev
< 0x80 && toupper(chPrev
) == 'E'));
64 typedef unsigned int sql_state_t
;
68 void Set(Sci_Position lineNumber
, unsigned short int sqlStatesLine
) {
69 sqlStatement
.Set(lineNumber
, sqlStatesLine
);
72 sql_state_t
IgnoreWhen (sql_state_t sqlStatesLine
, bool enable
) {
74 sqlStatesLine
|= MASK_IGNORE_WHEN
;
76 sqlStatesLine
&= ~MASK_IGNORE_WHEN
;
81 sql_state_t
IntoCondition (sql_state_t sqlStatesLine
, bool enable
) {
83 sqlStatesLine
|= MASK_INTO_CONDITION
;
85 sqlStatesLine
&= ~MASK_INTO_CONDITION
;
90 sql_state_t
IntoExceptionBlock (sql_state_t sqlStatesLine
, bool enable
) {
92 sqlStatesLine
|= MASK_INTO_EXCEPTION
;
94 sqlStatesLine
&= ~MASK_INTO_EXCEPTION
;
99 sql_state_t
IntoDeclareBlock (sql_state_t sqlStatesLine
, bool enable
) {
101 sqlStatesLine
|= MASK_INTO_DECLARE
;
103 sqlStatesLine
&= ~MASK_INTO_DECLARE
;
105 return sqlStatesLine
;
108 sql_state_t
IntoMergeStatement (sql_state_t sqlStatesLine
, bool enable
) {
110 sqlStatesLine
|= MASK_MERGE_STATEMENT
;
112 sqlStatesLine
&= ~MASK_MERGE_STATEMENT
;
114 return sqlStatesLine
;
117 sql_state_t
CaseMergeWithoutWhenFound (sql_state_t sqlStatesLine
, bool found
) {
119 sqlStatesLine
|= MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
;
121 sqlStatesLine
&= ~MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
;
123 return sqlStatesLine
;
125 sql_state_t
IntoSelectStatementOrAssignment (sql_state_t sqlStatesLine
, bool found
) {
127 sqlStatesLine
|= MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
;
129 sqlStatesLine
&= ~MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
;
130 return sqlStatesLine
;
133 sql_state_t
BeginCaseBlock (sql_state_t sqlStatesLine
) {
134 if ((sqlStatesLine
& MASK_NESTED_CASES
) < MASK_NESTED_CASES
) {
137 return sqlStatesLine
;
140 sql_state_t
EndCaseBlock (sql_state_t sqlStatesLine
) {
141 if ((sqlStatesLine
& MASK_NESTED_CASES
) > 0) {
144 return sqlStatesLine
;
147 sql_state_t
IntoCreateStatement (sql_state_t sqlStatesLine
, bool enable
) {
149 sqlStatesLine
|= MASK_INTO_CREATE
;
151 sqlStatesLine
&= ~MASK_INTO_CREATE
;
153 return sqlStatesLine
;
156 sql_state_t
IntoCreateViewStatement (sql_state_t sqlStatesLine
, bool enable
) {
158 sqlStatesLine
|= MASK_INTO_CREATE_VIEW
;
160 sqlStatesLine
&= ~MASK_INTO_CREATE_VIEW
;
162 return sqlStatesLine
;
165 sql_state_t
IntoCreateViewAsStatement (sql_state_t sqlStatesLine
, bool enable
) {
167 sqlStatesLine
|= MASK_INTO_CREATE_VIEW_AS_STATEMENT
;
169 sqlStatesLine
&= ~MASK_INTO_CREATE_VIEW_AS_STATEMENT
;
171 return sqlStatesLine
;
174 bool IsIgnoreWhen (sql_state_t sqlStatesLine
) {
175 return (sqlStatesLine
& MASK_IGNORE_WHEN
) != 0;
178 bool IsIntoCondition (sql_state_t sqlStatesLine
) {
179 return (sqlStatesLine
& MASK_INTO_CONDITION
) != 0;
182 bool IsIntoCaseBlock (sql_state_t sqlStatesLine
) {
183 return (sqlStatesLine
& MASK_NESTED_CASES
) != 0;
186 bool IsIntoExceptionBlock (sql_state_t sqlStatesLine
) {
187 return (sqlStatesLine
& MASK_INTO_EXCEPTION
) != 0;
189 bool IsIntoSelectStatementOrAssignment (sql_state_t sqlStatesLine
) {
190 return (sqlStatesLine
& MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
) != 0;
192 bool IsCaseMergeWithoutWhenFound (sql_state_t sqlStatesLine
) {
193 return (sqlStatesLine
& MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
) != 0;
196 bool IsIntoDeclareBlock (sql_state_t sqlStatesLine
) {
197 return (sqlStatesLine
& MASK_INTO_DECLARE
) != 0;
200 bool IsIntoMergeStatement (sql_state_t sqlStatesLine
) {
201 return (sqlStatesLine
& MASK_MERGE_STATEMENT
) != 0;
204 bool IsIntoCreateStatement (sql_state_t sqlStatesLine
) {
205 return (sqlStatesLine
& MASK_INTO_CREATE
) != 0;
208 bool IsIntoCreateViewStatement (sql_state_t sqlStatesLine
) {
209 return (sqlStatesLine
& MASK_INTO_CREATE_VIEW
) != 0;
212 bool IsIntoCreateViewAsStatement (sql_state_t sqlStatesLine
) {
213 return (sqlStatesLine
& MASK_INTO_CREATE_VIEW_AS_STATEMENT
) != 0;
216 sql_state_t
ForLine(Sci_Position lineNumber
) {
217 return sqlStatement
.ValueAt(lineNumber
);
223 SparseState
<sql_state_t
> sqlStatement
;
225 MASK_NESTED_CASES
= 0x0001FF,
226 MASK_INTO_SELECT_STATEMENT_OR_ASSIGNEMENT
= 0x000200,
227 MASK_CASE_MERGE_WITHOUT_WHEN_FOUND
= 0x000400,
228 MASK_MERGE_STATEMENT
= 0x000800,
229 MASK_INTO_DECLARE
= 0x001000,
230 MASK_INTO_EXCEPTION
= 0x002000,
231 MASK_INTO_CONDITION
= 0x004000,
232 MASK_IGNORE_WHEN
= 0x008000,
233 MASK_INTO_CREATE
= 0x010000,
234 MASK_INTO_CREATE_VIEW
= 0x020000,
235 MASK_INTO_CREATE_VIEW_AS_STATEMENT
= 0x040000
239 // Options used for LexerSQL
246 bool sqlBackticksIdentifier
;
247 bool sqlNumbersignComment
;
248 bool sqlBackslashEscapes
;
249 bool sqlAllowDottedWord
;
255 foldOnlyBegin
= false;
256 sqlBackticksIdentifier
= false;
257 sqlNumbersignComment
= false;
258 sqlBackslashEscapes
= false;
259 sqlAllowDottedWord
= false;
263 static const char * const sqlWordListDesc
[] = {
275 struct OptionSetSQL
: public OptionSet
<OptionsSQL
> {
277 DefineProperty("fold", &OptionsSQL::fold
);
279 DefineProperty("fold.sql.at.else", &OptionsSQL::foldAtElse
,
280 "This option enables SQL folding on a \"ELSE\" and \"ELSIF\" line of an IF statement.");
282 DefineProperty("fold.comment", &OptionsSQL::foldComment
);
284 DefineProperty("fold.compact", &OptionsSQL::foldCompact
);
286 DefineProperty("fold.sql.only.begin", &OptionsSQL::foldOnlyBegin
);
288 DefineProperty("lexer.sql.backticks.identifier", &OptionsSQL::sqlBackticksIdentifier
);
290 DefineProperty("lexer.sql.numbersign.comment", &OptionsSQL::sqlNumbersignComment
,
291 "If \"lexer.sql.numbersign.comment\" property is set to 0 a line beginning with '#' will not be a comment.");
293 DefineProperty("sql.backslash.escapes", &OptionsSQL::sqlBackslashEscapes
,
294 "Enables backslash as an escape character in SQL.");
296 DefineProperty("lexer.sql.allow.dotted.word", &OptionsSQL::sqlAllowDottedWord
,
297 "Set to 1 to colourise recognized words with dots "
298 "(recommended for Oracle PL/SQL objects).");
300 DefineWordListSets(sqlWordListDesc
);
304 class LexerSQL
: public DefaultLexer
{
308 virtual ~LexerSQL() {}
310 int SCI_METHOD
Version () const override
{
314 void SCI_METHOD
Release() override
{
318 const char * SCI_METHOD
PropertyNames() override
{
319 return osSQL
.PropertyNames();
322 int SCI_METHOD
PropertyType(const char *name
) override
{
323 return osSQL
.PropertyType(name
);
326 const char * SCI_METHOD
DescribeProperty(const char *name
) override
{
327 return osSQL
.DescribeProperty(name
);
330 Sci_Position SCI_METHOD
PropertySet(const char *key
, const char *val
) override
{
331 if (osSQL
.PropertySet(&options
, key
, val
)) {
337 const char * SCI_METHOD
DescribeWordListSets() override
{
338 return osSQL
.DescribeWordListSets();
341 Sci_Position SCI_METHOD
WordListSet(int n
, const char *wl
) override
;
342 void SCI_METHOD
Lex(Sci_PositionU startPos
, Sci_Position lengthDoc
, int initStyle
, IDocument
*pAccess
) override
;
343 void SCI_METHOD
Fold(Sci_PositionU startPos
, Sci_Position lengthDoc
, int initStyle
, IDocument
*pAccess
) override
;
345 void * SCI_METHOD
PrivateCall(int, void *) override
{
349 static ILexer4
*LexerFactorySQL() {
350 return new LexerSQL();
353 bool IsStreamCommentStyle(int style
) {
354 return style
== SCE_SQL_COMMENT
||
355 style
== SCE_SQL_COMMENTDOC
||
356 style
== SCE_SQL_COMMENTDOCKEYWORD
||
357 style
== SCE_SQL_COMMENTDOCKEYWORDERROR
;
360 bool IsCommentStyle (int style
) {
362 case SCE_SQL_COMMENT
:
363 case SCE_SQL_COMMENTDOC
:
364 case SCE_SQL_COMMENTLINE
:
365 case SCE_SQL_COMMENTLINEDOC
:
366 case SCE_SQL_COMMENTDOCKEYWORD
:
367 case SCE_SQL_COMMENTDOCKEYWORDERROR
:
374 bool IsCommentLine (Sci_Position line
, LexAccessor
&styler
) {
375 Sci_Position pos
= styler
.LineStart(line
);
376 Sci_Position eol_pos
= styler
.LineStart(line
+ 1) - 1;
377 for (Sci_Position i
= pos
; i
+ 1 < eol_pos
; i
++) {
378 int style
= styler
.StyleAt(i
);
379 // MySQL needs -- comments to be followed by space or control char
380 if (style
== SCE_SQL_COMMENTLINE
&& styler
.Match(i
, "--"))
382 else if (!IsASpaceOrTab(styler
[i
]))
402 Sci_Position SCI_METHOD
LexerSQL::WordListSet(int n
, const char *wl
) {
403 WordList
*wordListN
= 0;
406 wordListN
= &keywords1
;
409 wordListN
= &keywords2
;
412 wordListN
= &kw_pldoc
;
415 wordListN
= &kw_sqlplus
;
418 wordListN
= &kw_user1
;
421 wordListN
= &kw_user2
;
424 wordListN
= &kw_user3
;
427 wordListN
= &kw_user4
;
429 Sci_Position firstModification
= -1;
433 if (*wordListN
!= wlNew
) {
435 firstModification
= 0;
438 return firstModification
;
441 void SCI_METHOD
LexerSQL::Lex(Sci_PositionU startPos
, Sci_Position length
, int initStyle
, IDocument
*pAccess
) {
442 LexAccessor
styler(pAccess
);
443 StyleContext
sc(startPos
, length
, initStyle
, styler
);
444 int styleBeforeDCKeyword
= SCE_SQL_DEFAULT
;
445 Sci_Position offset
= 0;
447 for (; sc
.More(); sc
.Forward(), offset
++) {
448 // Determine if the current state should terminate.
450 case SCE_SQL_OPERATOR
:
451 sc
.SetState(SCE_SQL_DEFAULT
);
454 // We stop the number definition on non-numerical non-dot non-eE non-sign char
455 if (!IsANumberChar(sc
.ch
, sc
.chPrev
)) {
456 sc
.SetState(SCE_SQL_DEFAULT
);
459 case SCE_SQL_IDENTIFIER
:
460 if (!IsAWordChar(sc
.ch
, options
.sqlAllowDottedWord
)) {
461 int nextState
= SCE_SQL_DEFAULT
;
463 sc
.GetCurrentLowered(s
, sizeof(s
));
464 if (keywords1
.InList(s
)) {
465 sc
.ChangeState(SCE_SQL_WORD
);
466 } else if (keywords2
.InList(s
)) {
467 sc
.ChangeState(SCE_SQL_WORD2
);
468 } else if (kw_sqlplus
.InListAbbreviated(s
, '~')) {
469 sc
.ChangeState(SCE_SQL_SQLPLUS
);
470 if (strncmp(s
, "rem", 3) == 0) {
471 nextState
= SCE_SQL_SQLPLUS_COMMENT
;
472 } else if (strncmp(s
, "pro", 3) == 0) {
473 nextState
= SCE_SQL_SQLPLUS_PROMPT
;
475 } else if (kw_user1
.InList(s
)) {
476 sc
.ChangeState(SCE_SQL_USER1
);
477 } else if (kw_user2
.InList(s
)) {
478 sc
.ChangeState(SCE_SQL_USER2
);
479 } else if (kw_user3
.InList(s
)) {
480 sc
.ChangeState(SCE_SQL_USER3
);
481 } else if (kw_user4
.InList(s
)) {
482 sc
.ChangeState(SCE_SQL_USER4
);
484 sc
.SetState(nextState
);
487 case SCE_SQL_QUOTEDIDENTIFIER
:
489 if (sc
.chNext
== 0x60) {
490 sc
.Forward(); // Ignore it
492 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
496 case SCE_SQL_COMMENT
:
497 if (sc
.Match('*', '/')) {
499 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
502 case SCE_SQL_COMMENTDOC
:
503 if (sc
.Match('*', '/')) {
505 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
506 } else if (sc
.ch
== '@' || sc
.ch
== '\\') { // Doxygen support
507 // Verify that we have the conditions to mark a comment-doc-keyword
508 if ((IsASpace(sc
.chPrev
) || sc
.chPrev
== '*') && (!IsASpace(sc
.chNext
))) {
509 styleBeforeDCKeyword
= SCE_SQL_COMMENTDOC
;
510 sc
.SetState(SCE_SQL_COMMENTDOCKEYWORD
);
514 case SCE_SQL_COMMENTLINE
:
515 case SCE_SQL_COMMENTLINEDOC
:
516 case SCE_SQL_SQLPLUS_COMMENT
:
517 case SCE_SQL_SQLPLUS_PROMPT
:
518 if (sc
.atLineStart
) {
519 sc
.SetState(SCE_SQL_DEFAULT
);
522 case SCE_SQL_COMMENTDOCKEYWORD
:
523 if ((styleBeforeDCKeyword
== SCE_SQL_COMMENTDOC
) && sc
.Match('*', '/')) {
524 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
526 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
527 } else if (!IsADoxygenChar(sc
.ch
)) {
529 sc
.GetCurrentLowered(s
, sizeof(s
));
530 if (!isspace(sc
.ch
) || !kw_pldoc
.InList(s
+ 1)) {
531 sc
.ChangeState(SCE_SQL_COMMENTDOCKEYWORDERROR
);
533 sc
.SetState(styleBeforeDCKeyword
);
536 case SCE_SQL_CHARACTER
:
537 if (options
.sqlBackslashEscapes
&& sc
.ch
== '\\') {
539 } else if (sc
.ch
== '\'') {
540 if (sc
.chNext
== '\"') {
543 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
548 if (options
.sqlBackslashEscapes
&& sc
.ch
== '\\') {
551 } else if (sc
.ch
== '\"') {
552 if (sc
.chNext
== '\"') {
555 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
559 case SCE_SQL_QOPERATOR
:
560 // Locate the unique Q operator character
562 char qOperator
= 0x00;
563 for (Sci_Position styleStartPos
= sc
.currentPos
; styleStartPos
> 0; --styleStartPos
) {
564 if (styler
.StyleAt(styleStartPos
- 1) != SCE_SQL_QOPERATOR
) {
565 qOperator
= styler
.SafeGetCharAt(styleStartPos
+ 2);
570 char qComplement
= 0x00;
572 if (qOperator
== '<') {
574 } else if (qOperator
== '(') {
576 } else if (qOperator
== '{') {
578 } else if (qOperator
== '[') {
581 qComplement
= qOperator
;
584 if (sc
.Match(qComplement
, '\'')) {
586 sc
.ForwardSetState(SCE_SQL_DEFAULT
);
591 // Determine if a new state should be entered.
592 if (sc
.state
== SCE_SQL_DEFAULT
) {
593 if (sc
.Match('q', '\'') || sc
.Match('Q', '\'')) {
594 sc
.SetState(SCE_SQL_QOPERATOR
);
596 } else if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
)) ||
597 ((sc
.ch
== '-' || sc
.ch
== '+') && IsADigit(sc
.chNext
) && !IsADigit(sc
.chPrev
))) {
598 sc
.SetState(SCE_SQL_NUMBER
);
599 } else if (IsAWordStart(sc
.ch
)) {
600 sc
.SetState(SCE_SQL_IDENTIFIER
);
601 } else if (sc
.ch
== 0x60 && options
.sqlBackticksIdentifier
) {
602 sc
.SetState(SCE_SQL_QUOTEDIDENTIFIER
);
603 } else if (sc
.Match('/', '*')) {
604 if (sc
.Match("/**") || sc
.Match("/*!")) { // Support of Doxygen doc. style
605 sc
.SetState(SCE_SQL_COMMENTDOC
);
607 sc
.SetState(SCE_SQL_COMMENT
);
609 sc
.Forward(); // Eat the * so it isn't used for the end of the comment
610 } else if (sc
.Match('-', '-')) {
611 // MySQL requires a space or control char after --
612 // http://dev.mysql.com/doc/mysql/en/ansi-diff-comments.html
613 // Perhaps we should enforce that with proper property:
614 //~ } else if (sc.Match("-- ")) {
615 sc
.SetState(SCE_SQL_COMMENTLINE
);
616 } else if (sc
.ch
== '#' && options
.sqlNumbersignComment
) {
617 sc
.SetState(SCE_SQL_COMMENTLINEDOC
);
618 } else if (sc
.ch
== '\'') {
619 sc
.SetState(SCE_SQL_CHARACTER
);
620 } else if (sc
.ch
== '\"') {
621 sc
.SetState(SCE_SQL_STRING
);
622 } else if (isoperator(static_cast<char>(sc
.ch
))) {
623 sc
.SetState(SCE_SQL_OPERATOR
);
630 void SCI_METHOD
LexerSQL::Fold(Sci_PositionU startPos
, Sci_Position length
, int initStyle
, IDocument
*pAccess
) {
633 LexAccessor
styler(pAccess
);
634 Sci_PositionU endPos
= startPos
+ length
;
635 int visibleChars
= 0;
636 Sci_Position lineCurrent
= styler
.GetLine(startPos
);
637 int levelCurrent
= SC_FOLDLEVELBASE
;
639 if (lineCurrent
> 0) {
640 // Backtrack to previous line in case need to fix its fold status for folding block of single-line comments (i.e. '--').
641 Sci_Position lastNLPos
= -1;
642 // And keep going back until we find an operator ';' followed
643 // by white-space and/or comments. This will improve folding.
644 while (--startPos
> 0) {
645 char ch
= styler
[startPos
];
646 if (ch
== '\n' || (ch
== '\r' && styler
[startPos
+ 1] != '\n')) {
647 lastNLPos
= startPos
;
648 } else if (ch
== ';' &&
649 styler
.StyleAt(startPos
) == SCE_SQL_OPERATOR
) {
650 bool isAllClear
= true;
651 for (Sci_Position tempPos
= startPos
+ 1;
654 int tempStyle
= styler
.StyleAt(tempPos
);
655 if (!IsCommentStyle(tempStyle
)
656 && tempStyle
!= SCE_SQL_DEFAULT
) {
662 startPos
= lastNLPos
+ 1;
667 lineCurrent
= styler
.GetLine(startPos
);
669 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
671 // And because folding ends at ';', keep going until we find one
672 // Otherwise if create ... view ... as is split over multiple
673 // lines the folding won't always update immediately.
674 Sci_PositionU docLength
= styler
.Length();
675 for (; endPos
< docLength
; ++endPos
) {
676 if (styler
.SafeGetCharAt(endPos
) == ';') {
681 int levelNext
= levelCurrent
;
682 char chNext
= styler
[startPos
];
683 int styleNext
= styler
.StyleAt(startPos
);
684 int style
= initStyle
;
685 bool endFound
= false;
686 bool isUnfoldingIgnored
= false;
687 // this statementFound flag avoids to fold when the statement is on only one line by ignoring ELSE or ELSIF
688 // eg. "IF condition1 THEN ... ELSIF condition2 THEN ... ELSE ... END IF;"
689 bool statementFound
= false;
690 sql_state_t sqlStatesCurrentLine
= 0;
691 if (!options
.foldOnlyBegin
) {
692 sqlStatesCurrentLine
= sqlStates
.ForLine(lineCurrent
);
694 for (Sci_PositionU i
= startPos
; i
< endPos
; i
++) {
696 chNext
= styler
.SafeGetCharAt(i
+ 1);
697 int stylePrev
= style
;
699 styleNext
= styler
.StyleAt(i
+ 1);
700 bool atEOL
= (ch
== '\r' && chNext
!= '\n') || (ch
== '\n');
701 if (atEOL
|| (!IsCommentStyle(style
) && ch
== ';')) {
703 //Maybe this is the end of "EXCEPTION" BLOCK (eg. "BEGIN ... EXCEPTION ... END;")
704 sqlStatesCurrentLine
= sqlStates
.IntoExceptionBlock(sqlStatesCurrentLine
, false);
706 // set endFound and isUnfoldingIgnored to false if EOL is reached or ';' is found
708 isUnfoldingIgnored
= false;
710 if ((!IsCommentStyle(style
) && ch
== ';')) {
711 if (sqlStates
.IsIntoMergeStatement(sqlStatesCurrentLine
)) {
712 // This is the end of "MERGE" statement.
713 if (!sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
))
715 sqlStatesCurrentLine
= sqlStates
.IntoMergeStatement(sqlStatesCurrentLine
, false);
718 if (sqlStates
.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine
))
719 sqlStatesCurrentLine
= sqlStates
.IntoSelectStatementOrAssignment(sqlStatesCurrentLine
, false);
720 if (sqlStates
.IsIntoCreateStatement(sqlStatesCurrentLine
)) {
721 if (sqlStates
.IsIntoCreateViewStatement(sqlStatesCurrentLine
)) {
722 if (sqlStates
.IsIntoCreateViewAsStatement(sqlStatesCurrentLine
)) {
724 sqlStatesCurrentLine
= sqlStates
.IntoCreateViewAsStatement(sqlStatesCurrentLine
, false);
726 sqlStatesCurrentLine
= sqlStates
.IntoCreateViewStatement(sqlStatesCurrentLine
, false);
728 sqlStatesCurrentLine
= sqlStates
.IntoCreateStatement(sqlStatesCurrentLine
, false);
731 if (ch
== ':' && chNext
== '=' && !IsCommentStyle(style
))
732 sqlStatesCurrentLine
= sqlStates
.IntoSelectStatementOrAssignment(sqlStatesCurrentLine
, true);
734 if (options
.foldComment
&& IsStreamCommentStyle(style
)) {
735 if (!IsStreamCommentStyle(stylePrev
)) {
737 } else if (!IsStreamCommentStyle(styleNext
) && !atEOL
) {
738 // Comments don't end at end of line and the next character may be unstyled.
742 if (options
.foldComment
&& (style
== SCE_SQL_COMMENTLINE
)) {
743 // MySQL needs -- comments to be followed by space or control char
744 if ((ch
== '-') && (chNext
== '-')) {
745 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
746 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
747 if (chNext2
== '{' || chNext3
== '{') {
749 } else if (chNext2
== '}' || chNext3
== '}') {
754 // Fold block of single-line comments (i.e. '--').
755 if (options
.foldComment
&& atEOL
&& IsCommentLine(lineCurrent
, styler
)) {
756 if (!IsCommentLine(lineCurrent
- 1, styler
) && IsCommentLine(lineCurrent
+ 1, styler
))
758 else if (IsCommentLine(lineCurrent
- 1, styler
) && !IsCommentLine(lineCurrent
+ 1, styler
))
761 if (style
== SCE_SQL_OPERATOR
) {
763 if (levelCurrent
> levelNext
)
766 } else if (ch
== ')') {
768 } else if ((!options
.foldOnlyBegin
) && ch
== ';') {
769 sqlStatesCurrentLine
= sqlStates
.IgnoreWhen(sqlStatesCurrentLine
, false);
772 // If new keyword (cannot trigger on elseif or nullif, does less tests)
773 if (style
== SCE_SQL_WORD
&& stylePrev
!= SCE_SQL_WORD
) {
774 const int MAX_KW_LEN
= 9; // Maximum length of folding keywords
775 char s
[MAX_KW_LEN
+ 2];
777 for (; j
< MAX_KW_LEN
+ 1; j
++) {
778 if (!iswordchar(styler
[i
+ j
])) {
781 s
[j
] = static_cast<char>(tolower(styler
[i
+ j
]));
783 if (j
== MAX_KW_LEN
+ 1) {
784 // Keyword too long, don't test it
789 if (!options
.foldOnlyBegin
&&
790 strcmp(s
, "select") == 0) {
791 sqlStatesCurrentLine
= sqlStates
.IntoSelectStatementOrAssignment(sqlStatesCurrentLine
, true);
792 } else if (strcmp(s
, "if") == 0) {
795 if (options
.foldOnlyBegin
&& !isUnfoldingIgnored
) {
796 // this end isn't for begin block, but for if block ("end if;")
797 // so ignore previous "end" by increment levelNext.
801 if (!options
.foldOnlyBegin
)
802 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, true);
803 if (levelCurrent
> levelNext
) {
804 // doesn't include this line into the folding block
805 // because doesn't hide IF (eg "END; IF")
806 levelCurrent
= levelNext
;
809 } else if (!options
.foldOnlyBegin
&&
810 strcmp(s
, "then") == 0 &&
811 sqlStates
.IsIntoCondition(sqlStatesCurrentLine
)) {
812 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, false);
813 if (!options
.foldOnlyBegin
) {
814 if (levelCurrent
> levelNext
) {
815 levelCurrent
= levelNext
;
820 statementFound
= true;
821 } else if (levelCurrent
> levelNext
) {
822 // doesn't include this line into the folding block
823 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
824 levelCurrent
= levelNext
;
826 } else if (strcmp(s
, "loop") == 0 ||
827 strcmp(s
, "case") == 0) {
830 if (options
.foldOnlyBegin
&& !isUnfoldingIgnored
) {
831 // this end isn't for begin block, but for loop block ("end loop;") or case block ("end case;")
832 // so ignore previous "end" by increment levelNext.
835 if ((!options
.foldOnlyBegin
) && strcmp(s
, "case") == 0) {
836 sqlStatesCurrentLine
= sqlStates
.EndCaseBlock(sqlStatesCurrentLine
);
837 if (!sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
))
838 levelNext
--; //again for the "end case;" and block when
840 } else if (!options
.foldOnlyBegin
) {
841 if (strcmp(s
, "case") == 0) {
842 sqlStatesCurrentLine
= sqlStates
.BeginCaseBlock(sqlStatesCurrentLine
);
843 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, true);
846 if (levelCurrent
> levelNext
)
847 levelCurrent
= levelNext
;
852 statementFound
= true;
853 } else if (levelCurrent
> levelNext
) {
854 // doesn't include this line into the folding block
855 // because doesn't hide LOOP or CASE (eg "END; LOOP" or "END; CASE")
856 levelCurrent
= levelNext
;
858 } else if ((!options
.foldOnlyBegin
) && (
859 // folding for ELSE and ELSIF block only if foldAtElse is set
860 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
861 options
.foldAtElse
&& !statementFound
) && strcmp(s
, "elsif") == 0) {
862 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, true);
865 } else if ((!options
.foldOnlyBegin
) && (
866 // folding for ELSE and ELSIF block only if foldAtElse is set
867 // and IF or CASE aren't on only one line with ELSE or ELSIF (with flag statementFound)
868 options
.foldAtElse
&& !statementFound
) && strcmp(s
, "else") == 0) {
869 // prevent also ELSE is on the same line (eg. "ELSE ... END IF;")
870 statementFound
= true;
871 if (sqlStates
.IsIntoCaseBlock(sqlStatesCurrentLine
) && sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
)) {
872 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, false);
875 // we are in same case "} ELSE {" in C language
878 } else if (strcmp(s
, "begin") == 0) {
880 sqlStatesCurrentLine
= sqlStates
.IntoDeclareBlock(sqlStatesCurrentLine
, false);
881 } else if ((strcmp(s
, "end") == 0) ||
882 // SQL Anywhere permits IF ... ELSE ... ENDIF
883 // will only be active if "endif" appears in the
885 (strcmp(s
, "endif") == 0)) {
888 if (sqlStates
.IsIntoSelectStatementOrAssignment(sqlStatesCurrentLine
) && !sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
))
890 if (levelNext
< SC_FOLDLEVELBASE
) {
891 levelNext
= SC_FOLDLEVELBASE
;
892 isUnfoldingIgnored
= true;
894 } else if ((!options
.foldOnlyBegin
) &&
895 strcmp(s
, "when") == 0 &&
896 !sqlStates
.IsIgnoreWhen(sqlStatesCurrentLine
) &&
897 !sqlStates
.IsIntoExceptionBlock(sqlStatesCurrentLine
) && (
898 sqlStates
.IsIntoCaseBlock(sqlStatesCurrentLine
) ||
899 sqlStates
.IsIntoMergeStatement(sqlStatesCurrentLine
)
902 sqlStatesCurrentLine
= sqlStates
.IntoCondition(sqlStatesCurrentLine
, true);
904 // 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")
905 // and same way for MERGE statement.
906 if (!statementFound
) {
907 if (!sqlStates
.IsCaseMergeWithoutWhenFound(sqlStatesCurrentLine
)) {
911 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, false);
913 } else if ((!options
.foldOnlyBegin
) && strcmp(s
, "exit") == 0) {
914 sqlStatesCurrentLine
= sqlStates
.IgnoreWhen(sqlStatesCurrentLine
, true);
915 } else if ((!options
.foldOnlyBegin
) && !sqlStates
.IsIntoDeclareBlock(sqlStatesCurrentLine
) && strcmp(s
, "exception") == 0) {
916 sqlStatesCurrentLine
= sqlStates
.IntoExceptionBlock(sqlStatesCurrentLine
, true);
917 } else if ((!options
.foldOnlyBegin
) &&
918 (strcmp(s
, "declare") == 0 ||
919 strcmp(s
, "function") == 0 ||
920 strcmp(s
, "procedure") == 0 ||
921 strcmp(s
, "package") == 0)) {
922 sqlStatesCurrentLine
= sqlStates
.IntoDeclareBlock(sqlStatesCurrentLine
, true);
923 } else if ((!options
.foldOnlyBegin
) &&
924 strcmp(s
, "merge") == 0) {
925 sqlStatesCurrentLine
= sqlStates
.IntoMergeStatement(sqlStatesCurrentLine
, true);
926 sqlStatesCurrentLine
= sqlStates
.CaseMergeWithoutWhenFound(sqlStatesCurrentLine
, true);
928 statementFound
= true;
929 } else if ((!options
.foldOnlyBegin
) &&
930 strcmp(s
, "create") == 0) {
931 sqlStatesCurrentLine
= sqlStates
.IntoCreateStatement(sqlStatesCurrentLine
, true);
932 } else if ((!options
.foldOnlyBegin
) &&
933 strcmp(s
, "view") == 0 &&
934 sqlStates
.IsIntoCreateStatement(sqlStatesCurrentLine
)) {
935 sqlStatesCurrentLine
= sqlStates
.IntoCreateViewStatement(sqlStatesCurrentLine
, true);
936 } else if ((!options
.foldOnlyBegin
) &&
937 strcmp(s
, "as") == 0 &&
938 sqlStates
.IsIntoCreateViewStatement(sqlStatesCurrentLine
) &&
939 ! sqlStates
.IsIntoCreateViewAsStatement(sqlStatesCurrentLine
)) {
940 sqlStatesCurrentLine
= sqlStates
.IntoCreateViewAsStatement(sqlStatesCurrentLine
, true);
945 int levelUse
= levelCurrent
;
946 int lev
= levelUse
| levelNext
<< 16;
947 if (visibleChars
== 0 && options
.foldCompact
)
948 lev
|= SC_FOLDLEVELWHITEFLAG
;
949 if (levelUse
< levelNext
)
950 lev
|= SC_FOLDLEVELHEADERFLAG
;
951 if (lev
!= styler
.LevelAt(lineCurrent
)) {
952 styler
.SetLevel(lineCurrent
, lev
);
955 levelCurrent
= levelNext
;
957 statementFound
= false;
958 if (!options
.foldOnlyBegin
)
959 sqlStates
.Set(lineCurrent
, sqlStatesCurrentLine
);
961 if (!isspacechar(ch
)) {
967 LexerModule
lmSQL(SCLEX_SQL
, LexerSQL::LexerFactorySQL
, "sql", sqlWordListDesc
);