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 IsADoxygenChar(int ch
) {
44 return (islower(ch
) || ch
== '$' || ch
== '@' ||
45 ch
== '\\' || ch
== '&' || ch
== '<' ||
46 ch
== '>' || ch
== '#' || ch
== '{' ||
47 ch
== '}' || ch
== '[' || ch
== ']');
50 static inline bool IsANumberChar(int ch
) {
51 // Not exactly following number definition (several dots are seen as OK, etc.)
52 // but probably enough in most cases.
54 (isdigit(ch
) || toupper(ch
) == 'E' ||
55 ch
== '.' || ch
== '-' || ch
== '+');
58 //--------------------------------------------------------------------------------------------------
61 * Check if the current content context represent a keyword and set the context state if so.
63 static void CheckForKeyword(StyleContext
& sc
, WordList
* keywordlists
[], int activeState
)
65 int length
= sc
.LengthCurrent() + 1; // +1 for the next char
66 char* s
= new char[length
];
67 sc
.GetCurrentLowered(s
, length
);
68 if (keywordlists
[0]->InList(s
))
69 sc
.ChangeState(SCE_MYSQL_MAJORKEYWORD
| activeState
);
71 if (keywordlists
[1]->InList(s
))
72 sc
.ChangeState(SCE_MYSQL_KEYWORD
| activeState
);
74 if (keywordlists
[2]->InList(s
))
75 sc
.ChangeState(SCE_MYSQL_DATABASEOBJECT
| activeState
);
77 if (keywordlists
[3]->InList(s
))
78 sc
.ChangeState(SCE_MYSQL_FUNCTION
| activeState
);
80 if (keywordlists
[5]->InList(s
))
81 sc
.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD
| activeState
);
83 if (keywordlists
[6]->InList(s
))
84 sc
.ChangeState(SCE_MYSQL_USER1
| activeState
);
86 if (keywordlists
[7]->InList(s
))
87 sc
.ChangeState(SCE_MYSQL_USER2
| activeState
);
89 if (keywordlists
[8]->InList(s
))
90 sc
.ChangeState(SCE_MYSQL_USER3
| activeState
);
94 //--------------------------------------------------------------------------------------------------
96 #define HIDDENCOMMAND_STATE 0x40 // Offset for states within a hidden command.
97 #define MASKACTIVE(style) (style & ~HIDDENCOMMAND_STATE)
99 static void SetDefaultState(StyleContext
& sc
, int activeState
)
101 if (activeState
== 0)
102 sc
.SetState(SCE_MYSQL_DEFAULT
);
104 sc
.SetState(SCE_MYSQL_HIDDENCOMMAND
);
107 static void ForwardDefaultState(StyleContext
& sc
, int activeState
)
109 if (activeState
== 0)
110 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
112 sc
.ForwardSetState(SCE_MYSQL_HIDDENCOMMAND
);
115 static void ColouriseMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
118 StyleContext
sc(startPos
, length
, initStyle
, styler
, 127);
119 int activeState
= (initStyle
== SCE_MYSQL_HIDDENCOMMAND
) ? HIDDENCOMMAND_STATE
: initStyle
& HIDDENCOMMAND_STATE
;
121 for (; sc
.More(); sc
.Forward())
123 // Determine if the current state should terminate.
124 switch (MASKACTIVE(sc
.state
))
126 case SCE_MYSQL_OPERATOR
:
127 SetDefaultState(sc
, activeState
);
129 case SCE_MYSQL_NUMBER
:
130 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
131 if (!IsANumberChar(sc
.ch
))
132 SetDefaultState(sc
, activeState
);
134 case SCE_MYSQL_IDENTIFIER
:
135 // Switch from identifier to keyword state and open a new state for the new char.
136 if (!IsAWordChar(sc
.ch
))
138 CheckForKeyword(sc
, keywordlists
, activeState
);
140 // Additional check for function keywords needed.
141 // A function name must be followed by an opening parenthesis.
142 if (MASKACTIVE(sc
.state
) == SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
145 sc
.ChangeState(SCE_MYSQL_HIDDENCOMMAND
);
147 sc
.ChangeState(SCE_MYSQL_DEFAULT
);
150 SetDefaultState(sc
, activeState
);
153 case SCE_MYSQL_VARIABLE
:
154 if (!IsAWordChar(sc
.ch
))
155 SetDefaultState(sc
, activeState
);
157 case SCE_MYSQL_SYSTEMVARIABLE
:
158 if (!IsAWordChar(sc
.ch
))
160 int length
= sc
.LengthCurrent() + 1;
161 char* s
= new char[length
];
162 sc
.GetCurrentLowered(s
, length
);
164 // Check for known system variables here.
165 if (keywordlists
[4]->InList(&s
[2]))
166 sc
.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE
| activeState
);
169 SetDefaultState(sc
, activeState
);
172 case SCE_MYSQL_QUOTEDIDENTIFIER
:
175 if (sc
.chNext
== '`')
176 sc
.Forward(); // Ignore it
178 ForwardDefaultState(sc
, activeState
);
181 case SCE_MYSQL_COMMENT
:
182 if (sc
.Match('*', '/'))
185 ForwardDefaultState(sc
, activeState
);
188 case SCE_MYSQL_COMMENTLINE
:
190 SetDefaultState(sc
, activeState
);
192 case SCE_MYSQL_SQSTRING
:
194 sc
.Forward(); // Escape sequence
198 // End of single quoted string reached?
199 if (sc
.chNext
== '\'')
202 ForwardDefaultState(sc
, activeState
);
205 case SCE_MYSQL_DQSTRING
:
207 sc
.Forward(); // Escape sequence
211 // End of single quoted string reached?
212 if (sc
.chNext
== '\"')
215 ForwardDefaultState(sc
, activeState
);
218 case SCE_MYSQL_PLACEHOLDER
:
219 if (sc
.Match('}', '>'))
222 ForwardDefaultState(sc
, activeState
);
227 if (sc
.state
== SCE_MYSQL_HIDDENCOMMAND
&& sc
.Match('*', '/'))
231 ForwardDefaultState(sc
, activeState
);
234 // Determine if a new state should be entered.
235 if (sc
.state
== SCE_MYSQL_DEFAULT
|| sc
.state
== SCE_MYSQL_HIDDENCOMMAND
)
240 if (sc
.chNext
== '@')
242 sc
.SetState(SCE_MYSQL_SYSTEMVARIABLE
| activeState
);
243 sc
.Forward(2); // Skip past @@.
246 if (IsAWordStart(sc
.ch
))
248 sc
.SetState(SCE_MYSQL_VARIABLE
| activeState
);
249 sc
.Forward(); // Skip past @.
252 sc
.SetState(SCE_MYSQL_OPERATOR
| activeState
);
255 sc
.SetState(SCE_MYSQL_QUOTEDIDENTIFIER
| activeState
);
258 sc
.SetState(SCE_MYSQL_COMMENTLINE
| activeState
);
261 sc
.SetState(SCE_MYSQL_SQSTRING
| activeState
);
264 sc
.SetState(SCE_MYSQL_DQSTRING
| activeState
);
267 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
)))
268 sc
.SetState(SCE_MYSQL_NUMBER
| activeState
);
270 if (IsAWordStart(sc
.ch
))
271 sc
.SetState(SCE_MYSQL_IDENTIFIER
| activeState
);
273 if (sc
.Match('/', '*'))
275 sc
.SetState(SCE_MYSQL_COMMENT
| activeState
);
277 // Skip comment introducer and check for hidden command.
281 activeState
= HIDDENCOMMAND_STATE
;
282 sc
.ChangeState(SCE_MYSQL_HIDDENCOMMAND
);
285 else if (sc
.Match('<', '{'))
287 sc
.SetState(SCE_MYSQL_PLACEHOLDER
| activeState
);
292 // Special MySQL single line comment.
293 sc
.SetState(SCE_MYSQL_COMMENTLINE
| activeState
);
296 // Check the third character too. It must be a space or EOL.
297 if (sc
.ch
!= ' ' && sc
.ch
!= '\n' && sc
.ch
!= '\r')
298 sc
.ChangeState(SCE_MYSQL_OPERATOR
| activeState
);
301 if (isoperator(static_cast<char>(sc
.ch
)))
302 sc
.SetState(SCE_MYSQL_OPERATOR
| activeState
);
307 // Do a final check for keywords if we currently have an identifier, to highlight them
308 // also at the end of a line.
309 if (sc
.state
== SCE_MYSQL_IDENTIFIER
)
311 CheckForKeyword(sc
, keywordlists
, activeState
);
313 // Additional check for function keywords needed.
314 // A function name must be followed by an opening parenthesis.
315 if (sc
.state
== SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
316 SetDefaultState(sc
, activeState
);
322 //--------------------------------------------------------------------------------------------------
325 * Helper function to determine if we have a foldable comment currently.
327 static bool IsStreamCommentStyle(int style
)
329 return MASKACTIVE(style
) == SCE_MYSQL_COMMENT
;
332 //--------------------------------------------------------------------------------------------------
335 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
336 * companion to Match()...
338 bool MatchIgnoreCase(Accessor
&styler
, int currentPos
, const char *s
)
340 for (int n
= 0; *s
; n
++)
342 if (*s
!= tolower(styler
.SafeGetCharAt(currentPos
+ n
)))
349 //--------------------------------------------------------------------------------------------------
351 // Store both the current line's fold level and the next lines in the
352 // level store to make it easy to pick up with each increment.
353 static void FoldMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[], Accessor
&styler
)
355 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
356 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
357 bool foldOnlyBegin
= styler
.GetPropertyInt("fold.sql.only.begin", 0) != 0;
359 int visibleChars
= 0;
360 int lineCurrent
= styler
.GetLine(startPos
);
361 int levelCurrent
= SC_FOLDLEVELBASE
;
363 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
364 int levelNext
= levelCurrent
;
366 int styleNext
= styler
.StyleAt(startPos
);
367 int style
= initStyle
;
368 int activeState
= (style
== SCE_MYSQL_HIDDENCOMMAND
) ? HIDDENCOMMAND_STATE
: style
& HIDDENCOMMAND_STATE
;
370 bool endPending
= false;
371 bool whenPending
= false;
372 bool elseIfPending
= false;
374 char nextChar
= styler
.SafeGetCharAt(startPos
);
375 for (unsigned int i
= startPos
; length
> 0; i
++, length
--)
377 int stylePrev
= style
;
378 int lastActiveState
= activeState
;
380 styleNext
= styler
.StyleAt(i
+ 1);
381 activeState
= (style
== SCE_MYSQL_HIDDENCOMMAND
) ? HIDDENCOMMAND_STATE
: style
& HIDDENCOMMAND_STATE
;
383 char currentChar
= nextChar
;
384 nextChar
= styler
.SafeGetCharAt(i
+ 1);
385 bool atEOL
= (currentChar
== '\r' && nextChar
!= '\n') || (currentChar
== '\n');
387 switch (MASKACTIVE(style
))
389 case SCE_MYSQL_COMMENT
:
392 // Multiline comment style /* .. */ just started or is still in progress.
393 if (IsStreamCommentStyle(style
) && !IsStreamCommentStyle(stylePrev
))
397 case SCE_MYSQL_COMMENTLINE
:
400 // Not really a standard, but we add support for single line comments
401 // with special curly braces syntax as foldable comments too.
402 // MySQL needs -- comments to be followed by space or control char
403 if (styler
.Match(i
, "--"))
405 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
406 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
407 if (chNext2
== '{' || chNext3
== '{')
410 if (chNext2
== '}' || chNext3
== '}')
415 case SCE_MYSQL_HIDDENCOMMAND
:
419 // A conditional command is not a white space so it should end the current block
420 // before opening a new one.
423 if (levelNext < SC_FOLDLEVELBASE)
424 levelNext = SC_FOLDLEVELBASE;
427 if (activeState
!= lastActiveState
)
430 case SCE_MYSQL_OPERATOR
:
435 if (levelNext
< SC_FOLDLEVELBASE
)
436 levelNext
= SC_FOLDLEVELBASE
;
438 if (currentChar
== '(')
441 if (currentChar
== ')')
444 if (levelNext
< SC_FOLDLEVELBASE
)
445 levelNext
= SC_FOLDLEVELBASE
;
448 case SCE_MYSQL_MAJORKEYWORD
:
449 case SCE_MYSQL_KEYWORD
:
450 case SCE_MYSQL_FUNCTION
:
451 case SCE_MYSQL_PROCEDUREKEYWORD
:
452 // Reserved and other keywords.
453 if (style
!= stylePrev
)
455 // END decreases the folding level, regardless which keyword follows.
456 bool endFound
= MatchIgnoreCase(styler
, i
, "end");
460 if (levelNext
< SC_FOLDLEVELBASE
)
461 levelNext
= SC_FOLDLEVELBASE
;
466 if (MatchIgnoreCase(styler
, i
, "begin"))
472 bool whileFound
= MatchIgnoreCase(styler
, i
, "while");
473 bool loopFound
= MatchIgnoreCase(styler
, i
, "loop");
474 bool repeatFound
= MatchIgnoreCase(styler
, i
, "repeat");
475 bool caseFound
= MatchIgnoreCase(styler
, i
, "case");
477 if (whileFound
|| loopFound
|| repeatFound
|| caseFound
)
481 // IF alone does not increase the fold level as it is also used in non-block'ed
482 // code like DROP PROCEDURE blah IF EXISTS.
483 // Instead THEN opens the new level (if not part of an ELSEIF or WHEN (case) branch).
484 if (MatchIgnoreCase(styler
, i
, "then"))
486 if (!elseIfPending
&& !whenPending
)
490 elseIfPending
= false;
496 // Neither of if/then/while/loop/repeat/case, so check for
497 // sub parts of IF and CASE.
498 if (MatchIgnoreCase(styler
, i
, "elseif"))
499 elseIfPending
= true;
500 if (MatchIgnoreCase(styler
, i
, "when"))
508 // Keep the current end state for the next round.
509 endPending
= endFound
;
514 if (!isspacechar(currentChar
) && endPending
)
516 // END followed by a non-whitespace character (not covered by other cases like identifiers)
517 // also should end a folding block. Typical case: END followed by self defined delimiter.
519 if (levelNext
< SC_FOLDLEVELBASE
)
520 levelNext
= SC_FOLDLEVELBASE
;
525 // Go up one level if we just ended a multi line comment.
526 if (IsStreamCommentStyle(stylePrev
) && !IsStreamCommentStyle(style
))
529 if (levelNext
< SC_FOLDLEVELBASE
)
530 levelNext
= SC_FOLDLEVELBASE
;
533 if (activeState
== 0 && lastActiveState
!= 0)
535 // Decrease fold level when we left a hidden command.
537 if (levelNext
< SC_FOLDLEVELBASE
)
538 levelNext
= SC_FOLDLEVELBASE
;
543 // Apply the new folding level to this line.
544 // Leave pending states as they are otherwise a line break will de-sync
545 // code folding and valid syntax.
546 int levelUse
= levelCurrent
;
547 int lev
= levelUse
| levelNext
<< 16;
548 if (visibleChars
== 0 && foldCompact
)
549 lev
|= SC_FOLDLEVELWHITEFLAG
;
550 if (levelUse
< levelNext
)
551 lev
|= SC_FOLDLEVELHEADERFLAG
;
552 if (lev
!= styler
.LevelAt(lineCurrent
))
553 styler
.SetLevel(lineCurrent
, lev
);
556 levelCurrent
= levelNext
;
560 if (!isspacechar(currentChar
))
565 //--------------------------------------------------------------------------------------------------
567 static const char * const mysqlWordListDesc
[] = {
573 "Procedure keywords",
580 LexerModule
lmMySQL(SCLEX_MYSQL
, ColouriseMySQLDoc
, "mysql", FoldMySQLDoc
, mysqlWordListDesc
, 7);