2 * Scintilla source code edit control
6 * Improved by Mike Lischke <mike.lischke@sun.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
[])
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
);
71 if (keywordlists
[1]->InList(s
))
72 sc
.ChangeState(SCE_MYSQL_KEYWORD
);
74 if (keywordlists
[2]->InList(s
))
75 sc
.ChangeState(SCE_MYSQL_DATABASEOBJECT
);
77 if (keywordlists
[3]->InList(s
))
78 sc
.ChangeState(SCE_MYSQL_FUNCTION
);
80 if (keywordlists
[5]->InList(s
))
81 sc
.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD
);
83 if (keywordlists
[6]->InList(s
))
84 sc
.ChangeState(SCE_MYSQL_USER1
);
86 if (keywordlists
[7]->InList(s
))
87 sc
.ChangeState(SCE_MYSQL_USER2
);
89 if (keywordlists
[8]->InList(s
))
90 sc
.ChangeState(SCE_MYSQL_USER3
);
94 //--------------------------------------------------------------------------------------------------
96 static void ColouriseMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
99 StyleContext
sc(startPos
, length
, initStyle
, styler
);
101 for (; sc
.More(); sc
.Forward())
103 // Determine if the current state should terminate.
106 case SCE_MYSQL_OPERATOR
:
107 sc
.SetState(SCE_MYSQL_DEFAULT
);
109 case SCE_MYSQL_NUMBER
:
110 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
111 if (!IsANumberChar(sc
.ch
))
112 sc
.SetState(SCE_MYSQL_DEFAULT
);
114 case SCE_MYSQL_IDENTIFIER
:
115 // Switch from identifier to keyword state and open a new state for the new char.
116 if (!IsAWordChar(sc
.ch
))
118 CheckForKeyword(sc
, keywordlists
);
120 // Additional check for function keywords needed.
121 // A function name must be followed by an opening parenthesis.
122 if (sc
.state
== SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
123 sc
.ChangeState(SCE_MYSQL_DEFAULT
);
125 sc
.SetState(SCE_MYSQL_DEFAULT
);
128 case SCE_MYSQL_VARIABLE
:
129 if (!IsAWordChar(sc
.ch
))
130 sc
.SetState(SCE_MYSQL_DEFAULT
);
132 case SCE_MYSQL_SYSTEMVARIABLE
:
133 if (!IsAWordChar(sc
.ch
))
135 int length
= sc
.LengthCurrent() + 1;
136 char* s
= new char[length
];
137 sc
.GetCurrentLowered(s
, length
);
139 // Check for known system variables here.
140 if (keywordlists
[4]->InList(&s
[2]))
141 sc
.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE
);
144 sc
.SetState(SCE_MYSQL_DEFAULT
);
147 case SCE_MYSQL_QUOTEDIDENTIFIER
:
150 if (sc
.chNext
== '`')
151 sc
.Forward(); // Ignore it
153 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
156 case SCE_MYSQL_COMMENT
:
157 case SCE_MYSQL_HIDDENCOMMAND
:
158 if (sc
.Match('*', '/'))
161 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
164 case SCE_MYSQL_COMMENTLINE
:
166 sc
.SetState(SCE_MYSQL_DEFAULT
);
168 case SCE_MYSQL_SQSTRING
:
170 sc
.Forward(); // Escape sequence
174 // End of single quoted string reached?
175 if (sc
.chNext
== '\'')
178 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
181 case SCE_MYSQL_DQSTRING
:
183 sc
.Forward(); // Escape sequence
187 // End of single quoted string reached?
188 if (sc
.chNext
== '\"')
191 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
196 // Determine if a new state should be entered.
197 if (sc
.state
== SCE_MYSQL_DEFAULT
)
202 if (sc
.chNext
== '@')
204 sc
.SetState(SCE_MYSQL_SYSTEMVARIABLE
);
205 sc
.Forward(2); // Skip past @@.
208 if (IsAWordStart(sc
.ch
))
210 sc
.SetState(SCE_MYSQL_VARIABLE
);
211 sc
.Forward(); // Skip past @.
214 sc
.SetState(SCE_MYSQL_OPERATOR
);
217 sc
.SetState(SCE_MYSQL_QUOTEDIDENTIFIER
);
220 sc
.SetState(SCE_MYSQL_COMMENTLINE
);
223 sc
.SetState(SCE_MYSQL_SQSTRING
);
226 sc
.SetState(SCE_MYSQL_DQSTRING
);
229 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
)))
230 sc
.SetState(SCE_MYSQL_NUMBER
);
232 if (IsAWordStart(sc
.ch
))
233 sc
.SetState(SCE_MYSQL_IDENTIFIER
);
235 if (sc
.Match('/', '*'))
237 sc
.SetState(SCE_MYSQL_COMMENT
);
239 // Skip comment introducer and check for hidden command.
243 sc
.ChangeState(SCE_MYSQL_HIDDENCOMMAND
);
250 // Special MySQL single line comment.
251 sc
.SetState(SCE_MYSQL_COMMENTLINE
);
254 // Check the third character too. It must be a space or EOL.
255 if (sc
.ch
!= ' ' && sc
.ch
!= '\n' && sc
.ch
!= '\r')
256 sc
.ChangeState(SCE_MYSQL_OPERATOR
);
259 if (isoperator(static_cast<char>(sc
.ch
)))
260 sc
.SetState(SCE_MYSQL_OPERATOR
);
265 // Do a final check for keywords if we currently have an identifier, to highlight them
266 // also at the end of a line.
267 if (sc
.state
== SCE_MYSQL_IDENTIFIER
)
269 CheckForKeyword(sc
, keywordlists
);
271 // Additional check for function keywords needed.
272 // A function name must be followed by an opening parenthesis.
273 if (sc
.state
== SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
274 sc
.ChangeState(SCE_MYSQL_DEFAULT
);
280 //--------------------------------------------------------------------------------------------------
283 * Helper function to determine if we have a foldable comment currently.
285 static bool IsStreamCommentStyle(int style
)
287 return style
== SCE_MYSQL_COMMENT
;
290 //--------------------------------------------------------------------------------------------------
293 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
294 * companion to Match()...
296 bool MatchIgnoreCase(Accessor
&styler
, int currentPos
, const char *s
)
298 for (int n
= 0; *s
; n
++)
300 if (*s
!= tolower(styler
.SafeGetCharAt(currentPos
+ n
)))
307 //--------------------------------------------------------------------------------------------------
309 // Store both the current line's fold level and the next lines in the
310 // level store to make it easy to pick up with each increment.
311 static void FoldMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[], Accessor
&styler
)
313 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
314 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
315 bool foldOnlyBegin
= styler
.GetPropertyInt("fold.sql.only.begin", 0) != 0;
317 int visibleChars
= 0;
318 int lineCurrent
= styler
.GetLine(startPos
);
319 int levelCurrent
= SC_FOLDLEVELBASE
;
321 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
322 int levelNext
= levelCurrent
;
324 int styleNext
= styler
.StyleAt(startPos
);
325 int style
= initStyle
;
327 bool endPending
= false;
328 bool whenPending
= false;
329 bool elseIfPending
= false;
331 char nextChar
= styler
.SafeGetCharAt(startPos
);
332 for (unsigned int i
= startPos
; length
> 0; i
++, length
--)
334 int stylePrev
= style
;
336 styleNext
= styler
.StyleAt(i
+ 1);
338 char currentChar
= nextChar
;
339 nextChar
= styler
.SafeGetCharAt(i
+ 1);
340 bool atEOL
= (currentChar
== '\r' && nextChar
!= '\n') || (currentChar
== '\n');
344 case SCE_MYSQL_COMMENT
:
347 // Multiline comment style /* .. */.
348 if (IsStreamCommentStyle(style
))
350 // Increase level if we just start a foldable comment.
351 if (!IsStreamCommentStyle(stylePrev
))
354 // If we are in the middle of a foldable comment check if it ends now.
355 // Don't end at the line end, though.
356 if (!IsStreamCommentStyle(styleNext
) && !atEOL
)
361 case SCE_MYSQL_COMMENTLINE
:
364 // Not really a standard, but we add support for single line comments
365 // with special curly braces syntax as foldable comments too.
366 // MySQL needs -- comments to be followed by space or control char
367 if (styler
.Match(i
, "--"))
369 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
370 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
371 if (chNext2
== '{' || chNext3
== '{')
374 if (chNext2
== '}' || chNext3
== '}')
379 case SCE_MYSQL_HIDDENCOMMAND
:
382 // A conditional command is not a white space so it should end the current block
383 // before opening a new one.
386 if (levelNext
< SC_FOLDLEVELBASE
)
387 levelNext
= SC_FOLDLEVELBASE
;
389 if (style
!= stylePrev
)
392 if (style
!= styleNext
)
395 if (levelNext
< SC_FOLDLEVELBASE
)
396 levelNext
= SC_FOLDLEVELBASE
;
399 case SCE_MYSQL_OPERATOR
:
404 if (levelNext
< SC_FOLDLEVELBASE
)
405 levelNext
= SC_FOLDLEVELBASE
;
407 if (currentChar
== '(')
410 if (currentChar
== ')')
413 if (levelNext
< SC_FOLDLEVELBASE
)
414 levelNext
= SC_FOLDLEVELBASE
;
417 case SCE_MYSQL_MAJORKEYWORD
:
418 case SCE_MYSQL_KEYWORD
:
419 case SCE_MYSQL_FUNCTION
:
420 case SCE_MYSQL_PROCEDUREKEYWORD
:
421 // Reserved and other keywords.
422 if (style
!= stylePrev
)
424 // END decreases the folding level, regardless which keyword follows.
425 bool endFound
= MatchIgnoreCase(styler
, i
, "end");
429 if (levelNext
< SC_FOLDLEVELBASE
)
430 levelNext
= SC_FOLDLEVELBASE
;
435 if (MatchIgnoreCase(styler
, i
, "begin"))
441 bool whileFound
= MatchIgnoreCase(styler
, i
, "while");
442 bool loopFound
= MatchIgnoreCase(styler
, i
, "loop");
443 bool repeatFound
= MatchIgnoreCase(styler
, i
, "repeat");
444 bool caseFound
= MatchIgnoreCase(styler
, i
, "case");
446 if (whileFound
|| loopFound
|| repeatFound
|| caseFound
)
450 // IF alone does not increase the fold level as it is also used in non-block'ed
451 // code like DROP PROCEDURE blah IF EXISTS.
452 // Instead THEN opens the new level (if not part of an ELSEIF or WHEN (case) branch).
453 if (MatchIgnoreCase(styler
, i
, "then"))
455 if (!elseIfPending
&& !whenPending
)
459 elseIfPending
= false;
465 // Neither of if/then/while/loop/repeat/case, so check for
466 // sub parts of IF and CASE.
467 if (MatchIgnoreCase(styler
, i
, "elseif"))
468 elseIfPending
= true;
469 if (MatchIgnoreCase(styler
, i
, "when"))
477 // Keep the current end state for the next round.
478 endPending
= endFound
;
483 if (!isspace(currentChar
) && endPending
)
485 // END followed by a non-whitespace character (not covered by other cases like identifiers)
486 // also should end a folding block. Typical case: END followed by self defined delimiter.
488 if (levelNext
< SC_FOLDLEVELBASE
)
489 levelNext
= SC_FOLDLEVELBASE
;
496 // Apply the new folding level to this line.
497 // Leave pending states as they are otherwise a line break will de-sync
498 // code folding and valid syntax.
499 int levelUse
= levelCurrent
;
500 int lev
= levelUse
| levelNext
<< 16;
501 if (visibleChars
== 0 && foldCompact
)
502 lev
|= SC_FOLDLEVELWHITEFLAG
;
503 if (levelUse
< levelNext
)
504 lev
|= SC_FOLDLEVELHEADERFLAG
;
505 if (lev
!= styler
.LevelAt(lineCurrent
))
506 styler
.SetLevel(lineCurrent
, lev
);
509 levelCurrent
= levelNext
;
513 if (!isspacechar(currentChar
))
518 //--------------------------------------------------------------------------------------------------
520 static const char * const mysqlWordListDesc
[] = {
526 "Procedure keywords",
533 LexerModule
lmMySQL(SCLEX_MYSQL
, ColouriseMySQLDoc
, "mysql", FoldMySQLDoc
, mysqlWordListDesc
);