2 * Scintilla source code edit control
6 * Improved by Mike Lischke <mike.lischke@oracle.com>
7 * Adopted from LexSQL.cxx by Anders Karlsson <anders@mysql.com>
8 * Original work by Neil Hodgson <neilh@scintilla.org>
9 * Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
10 * The License.txt file describes the conditions under which this software may be distributed.
21 #include "Scintilla.h"
25 #include "LexAccessor.h"
27 #include "StyleContext.h"
28 #include "CharacterSet.h"
29 #include "LexerModule.h"
32 using namespace Scintilla
;
35 static inline bool IsAWordChar(int ch
) {
36 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_');
39 static inline bool IsAWordStart(int ch
) {
40 return (ch
< 0x80) && (isalpha(ch
) || ch
== '_');
43 static inline bool IsANumberChar(int ch
) {
44 // Not exactly following number definition (several dots are seen as OK, etc.)
45 // but probably enough in most cases.
47 (isdigit(ch
) || toupper(ch
) == 'E' ||
48 ch
== '.' || ch
== '-' || ch
== '+');
51 //--------------------------------------------------------------------------------------------------
54 * Check if the current content context represent a keyword and set the context state if so.
56 static void CheckForKeyword(StyleContext
& sc
, WordList
* keywordlists
[], int activeState
)
58 int length
= sc
.LengthCurrent() + 1; // +1 for the next char
59 char* s
= new char[length
];
60 sc
.GetCurrentLowered(s
, length
);
61 if (keywordlists
[0]->InList(s
))
62 sc
.ChangeState(SCE_MYSQL_MAJORKEYWORD
| activeState
);
64 if (keywordlists
[1]->InList(s
))
65 sc
.ChangeState(SCE_MYSQL_KEYWORD
| activeState
);
67 if (keywordlists
[2]->InList(s
))
68 sc
.ChangeState(SCE_MYSQL_DATABASEOBJECT
| activeState
);
70 if (keywordlists
[3]->InList(s
))
71 sc
.ChangeState(SCE_MYSQL_FUNCTION
| activeState
);
73 if (keywordlists
[5]->InList(s
))
74 sc
.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD
| activeState
);
76 if (keywordlists
[6]->InList(s
))
77 sc
.ChangeState(SCE_MYSQL_USER1
| activeState
);
79 if (keywordlists
[7]->InList(s
))
80 sc
.ChangeState(SCE_MYSQL_USER2
| activeState
);
82 if (keywordlists
[8]->InList(s
))
83 sc
.ChangeState(SCE_MYSQL_USER3
| activeState
);
87 //--------------------------------------------------------------------------------------------------
89 #define HIDDENCOMMAND_STATE 0x40 // Offset for states within a hidden command.
90 #define MASKACTIVE(style) (style & ~HIDDENCOMMAND_STATE)
92 static void SetDefaultState(StyleContext
& sc
, int activeState
)
95 sc
.SetState(SCE_MYSQL_DEFAULT
);
97 sc
.SetState(SCE_MYSQL_HIDDENCOMMAND
);
100 static void ForwardDefaultState(StyleContext
& sc
, int activeState
)
102 if (activeState
== 0)
103 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
105 sc
.ForwardSetState(SCE_MYSQL_HIDDENCOMMAND
);
108 static void ColouriseMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
111 StyleContext
sc(startPos
, length
, initStyle
, styler
, 127);
112 int activeState
= (initStyle
== SCE_MYSQL_HIDDENCOMMAND
) ? HIDDENCOMMAND_STATE
: initStyle
& HIDDENCOMMAND_STATE
;
114 for (; sc
.More(); sc
.Forward())
116 // Determine if the current state should terminate.
117 switch (MASKACTIVE(sc
.state
))
119 case SCE_MYSQL_OPERATOR
:
120 SetDefaultState(sc
, activeState
);
122 case SCE_MYSQL_NUMBER
:
123 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
124 if (!IsANumberChar(sc
.ch
))
125 SetDefaultState(sc
, activeState
);
127 case SCE_MYSQL_IDENTIFIER
:
128 // Switch from identifier to keyword state and open a new state for the new char.
129 if (!IsAWordChar(sc
.ch
))
131 CheckForKeyword(sc
, keywordlists
, activeState
);
133 // Additional check for function keywords needed.
134 // A function name must be followed by an opening parenthesis.
135 if (MASKACTIVE(sc
.state
) == SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
138 sc
.ChangeState(SCE_MYSQL_HIDDENCOMMAND
);
140 sc
.ChangeState(SCE_MYSQL_DEFAULT
);
143 SetDefaultState(sc
, activeState
);
146 case SCE_MYSQL_VARIABLE
:
147 if (!IsAWordChar(sc
.ch
))
148 SetDefaultState(sc
, activeState
);
150 case SCE_MYSQL_SYSTEMVARIABLE
:
151 if (!IsAWordChar(sc
.ch
))
153 int length
= sc
.LengthCurrent() + 1;
154 char* s
= new char[length
];
155 sc
.GetCurrentLowered(s
, length
);
157 // Check for known system variables here.
158 if (keywordlists
[4]->InList(&s
[2]))
159 sc
.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE
| activeState
);
162 SetDefaultState(sc
, activeState
);
165 case SCE_MYSQL_QUOTEDIDENTIFIER
:
168 if (sc
.chNext
== '`')
169 sc
.Forward(); // Ignore it
171 ForwardDefaultState(sc
, activeState
);
174 case SCE_MYSQL_COMMENT
:
175 if (sc
.Match('*', '/'))
178 ForwardDefaultState(sc
, activeState
);
181 case SCE_MYSQL_COMMENTLINE
:
183 SetDefaultState(sc
, activeState
);
185 case SCE_MYSQL_SQSTRING
:
187 sc
.Forward(); // Escape sequence
191 // End of single quoted string reached?
192 if (sc
.chNext
== '\'')
195 ForwardDefaultState(sc
, activeState
);
198 case SCE_MYSQL_DQSTRING
:
200 sc
.Forward(); // Escape sequence
204 // End of single quoted string reached?
205 if (sc
.chNext
== '\"')
208 ForwardDefaultState(sc
, activeState
);
211 case SCE_MYSQL_PLACEHOLDER
:
212 if (sc
.Match('}', '>'))
215 ForwardDefaultState(sc
, activeState
);
220 if (sc
.state
== SCE_MYSQL_HIDDENCOMMAND
&& sc
.Match('*', '/'))
224 ForwardDefaultState(sc
, activeState
);
227 // Determine if a new state should be entered.
228 if (sc
.state
== SCE_MYSQL_DEFAULT
|| sc
.state
== SCE_MYSQL_HIDDENCOMMAND
)
233 if (sc
.chNext
== '@')
235 sc
.SetState(SCE_MYSQL_SYSTEMVARIABLE
| activeState
);
236 sc
.Forward(2); // Skip past @@.
239 if (IsAWordStart(sc
.ch
))
241 sc
.SetState(SCE_MYSQL_VARIABLE
| activeState
);
242 sc
.Forward(); // Skip past @.
245 sc
.SetState(SCE_MYSQL_OPERATOR
| activeState
);
248 sc
.SetState(SCE_MYSQL_QUOTEDIDENTIFIER
| activeState
);
251 sc
.SetState(SCE_MYSQL_COMMENTLINE
| activeState
);
254 sc
.SetState(SCE_MYSQL_SQSTRING
| activeState
);
257 sc
.SetState(SCE_MYSQL_DQSTRING
| activeState
);
260 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
)))
261 sc
.SetState(SCE_MYSQL_NUMBER
| activeState
);
263 if (IsAWordStart(sc
.ch
))
264 sc
.SetState(SCE_MYSQL_IDENTIFIER
| activeState
);
266 if (sc
.Match('/', '*'))
268 sc
.SetState(SCE_MYSQL_COMMENT
| activeState
);
270 // Skip comment introducer and check for hidden command.
274 activeState
= HIDDENCOMMAND_STATE
;
275 sc
.ChangeState(SCE_MYSQL_HIDDENCOMMAND
);
278 else if (sc
.Match('<', '{'))
280 sc
.SetState(SCE_MYSQL_PLACEHOLDER
| activeState
);
285 // Special MySQL single line comment.
286 sc
.SetState(SCE_MYSQL_COMMENTLINE
| activeState
);
289 // Check the third character too. It must be a space or EOL.
290 if (sc
.ch
!= ' ' && sc
.ch
!= '\n' && sc
.ch
!= '\r')
291 sc
.ChangeState(SCE_MYSQL_OPERATOR
| activeState
);
294 if (isoperator(static_cast<char>(sc
.ch
)))
295 sc
.SetState(SCE_MYSQL_OPERATOR
| activeState
);
300 // Do a final check for keywords if we currently have an identifier, to highlight them
301 // also at the end of a line.
302 if (sc
.state
== SCE_MYSQL_IDENTIFIER
)
304 CheckForKeyword(sc
, keywordlists
, activeState
);
306 // Additional check for function keywords needed.
307 // A function name must be followed by an opening parenthesis.
308 if (sc
.state
== SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
309 SetDefaultState(sc
, activeState
);
315 //--------------------------------------------------------------------------------------------------
318 * Helper function to determine if we have a foldable comment currently.
320 static bool IsStreamCommentStyle(int style
)
322 return MASKACTIVE(style
) == SCE_MYSQL_COMMENT
;
325 //--------------------------------------------------------------------------------------------------
328 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
329 * companion to Match()...
331 bool MatchIgnoreCase(Accessor
&styler
, int currentPos
, const char *s
)
333 for (int n
= 0; *s
; n
++)
335 if (*s
!= tolower(styler
.SafeGetCharAt(currentPos
+ n
)))
342 //--------------------------------------------------------------------------------------------------
344 // Store both the current line's fold level and the next lines in the
345 // level store to make it easy to pick up with each increment.
346 static void FoldMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[], Accessor
&styler
)
348 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
349 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
350 bool foldOnlyBegin
= styler
.GetPropertyInt("fold.sql.only.begin", 0) != 0;
352 int visibleChars
= 0;
353 int lineCurrent
= styler
.GetLine(startPos
);
354 int levelCurrent
= SC_FOLDLEVELBASE
;
356 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
357 int levelNext
= levelCurrent
;
359 int styleNext
= styler
.StyleAt(startPos
);
360 int style
= initStyle
;
361 int activeState
= (style
== SCE_MYSQL_HIDDENCOMMAND
) ? HIDDENCOMMAND_STATE
: style
& HIDDENCOMMAND_STATE
;
363 bool endPending
= false;
364 bool whenPending
= false;
365 bool elseIfPending
= false;
367 char nextChar
= styler
.SafeGetCharAt(startPos
);
368 for (unsigned int i
= startPos
; length
> 0; i
++, length
--)
370 int stylePrev
= style
;
371 int lastActiveState
= activeState
;
373 styleNext
= styler
.StyleAt(i
+ 1);
374 activeState
= (style
== SCE_MYSQL_HIDDENCOMMAND
) ? HIDDENCOMMAND_STATE
: style
& HIDDENCOMMAND_STATE
;
376 char currentChar
= nextChar
;
377 nextChar
= styler
.SafeGetCharAt(i
+ 1);
378 bool atEOL
= (currentChar
== '\r' && nextChar
!= '\n') || (currentChar
== '\n');
380 switch (MASKACTIVE(style
))
382 case SCE_MYSQL_COMMENT
:
385 // Multiline comment style /* .. */ just started or is still in progress.
386 if (IsStreamCommentStyle(style
) && !IsStreamCommentStyle(stylePrev
))
390 case SCE_MYSQL_COMMENTLINE
:
393 // Not really a standard, but we add support for single line comments
394 // with special curly braces syntax as foldable comments too.
395 // MySQL needs -- comments to be followed by space or control char
396 if (styler
.Match(i
, "--"))
398 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
399 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
400 if (chNext2
== '{' || chNext3
== '{')
403 if (chNext2
== '}' || chNext3
== '}')
408 case SCE_MYSQL_HIDDENCOMMAND
:
412 // A conditional command is not a white space so it should end the current block
413 // before opening a new one.
416 if (levelNext < SC_FOLDLEVELBASE)
417 levelNext = SC_FOLDLEVELBASE;
420 if (activeState
!= lastActiveState
)
423 case SCE_MYSQL_OPERATOR
:
428 if (levelNext
< SC_FOLDLEVELBASE
)
429 levelNext
= SC_FOLDLEVELBASE
;
431 if (currentChar
== '(')
434 if (currentChar
== ')')
437 if (levelNext
< SC_FOLDLEVELBASE
)
438 levelNext
= SC_FOLDLEVELBASE
;
441 case SCE_MYSQL_MAJORKEYWORD
:
442 case SCE_MYSQL_KEYWORD
:
443 case SCE_MYSQL_FUNCTION
:
444 case SCE_MYSQL_PROCEDUREKEYWORD
:
445 // Reserved and other keywords.
446 if (style
!= stylePrev
)
448 // END decreases the folding level, regardless which keyword follows.
449 bool endFound
= MatchIgnoreCase(styler
, i
, "end");
453 if (levelNext
< SC_FOLDLEVELBASE
)
454 levelNext
= SC_FOLDLEVELBASE
;
459 if (MatchIgnoreCase(styler
, i
, "begin"))
465 bool whileFound
= MatchIgnoreCase(styler
, i
, "while");
466 bool loopFound
= MatchIgnoreCase(styler
, i
, "loop");
467 bool repeatFound
= MatchIgnoreCase(styler
, i
, "repeat");
468 bool caseFound
= MatchIgnoreCase(styler
, i
, "case");
470 if (whileFound
|| loopFound
|| repeatFound
|| caseFound
)
474 // IF alone does not increase the fold level as it is also used in non-block'ed
475 // code like DROP PROCEDURE blah IF EXISTS.
476 // Instead THEN opens the new level (if not part of an ELSEIF or WHEN (case) branch).
477 if (MatchIgnoreCase(styler
, i
, "then"))
479 if (!elseIfPending
&& !whenPending
)
483 elseIfPending
= false;
489 // Neither of if/then/while/loop/repeat/case, so check for
490 // sub parts of IF and CASE.
491 if (MatchIgnoreCase(styler
, i
, "elseif"))
492 elseIfPending
= true;
493 if (MatchIgnoreCase(styler
, i
, "when"))
501 // Keep the current end state for the next round.
502 endPending
= endFound
;
507 if (!isspacechar(currentChar
) && endPending
)
509 // END followed by a non-whitespace character (not covered by other cases like identifiers)
510 // also should end a folding block. Typical case: END followed by self defined delimiter.
512 if (levelNext
< SC_FOLDLEVELBASE
)
513 levelNext
= SC_FOLDLEVELBASE
;
518 // Go up one level if we just ended a multi line comment.
519 if (IsStreamCommentStyle(stylePrev
) && !IsStreamCommentStyle(style
))
522 if (levelNext
< SC_FOLDLEVELBASE
)
523 levelNext
= SC_FOLDLEVELBASE
;
526 if (activeState
== 0 && lastActiveState
!= 0)
528 // Decrease fold level when we left a hidden command.
530 if (levelNext
< SC_FOLDLEVELBASE
)
531 levelNext
= SC_FOLDLEVELBASE
;
536 // Apply the new folding level to this line.
537 // Leave pending states as they are otherwise a line break will de-sync
538 // code folding and valid syntax.
539 int levelUse
= levelCurrent
;
540 int lev
= levelUse
| levelNext
<< 16;
541 if (visibleChars
== 0 && foldCompact
)
542 lev
|= SC_FOLDLEVELWHITEFLAG
;
543 if (levelUse
< levelNext
)
544 lev
|= SC_FOLDLEVELHEADERFLAG
;
545 if (lev
!= styler
.LevelAt(lineCurrent
))
546 styler
.SetLevel(lineCurrent
, lev
);
549 levelCurrent
= levelNext
;
553 if (!isspacechar(currentChar
))
558 //--------------------------------------------------------------------------------------------------
560 static const char * const mysqlWordListDesc
[] = {
566 "Procedure keywords",
573 LexerModule
lmMySQL(SCLEX_MYSQL
, ColouriseMySQLDoc
, "mysql", FoldMySQLDoc
, mysqlWordListDesc
);