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.
23 #include "StyleContext.h"
25 #include "Scintilla.h"
29 using namespace Scintilla
;
32 static inline bool IsAWordChar(int ch
) {
33 return (ch
< 0x80) && (isalnum(ch
) || ch
== '_');
36 static inline bool IsAWordStart(int ch
) {
37 return (ch
< 0x80) && (isalpha(ch
) || ch
== '_');
40 static inline bool IsADoxygenChar(int ch
) {
41 return (islower(ch
) || ch
== '$' || ch
== '@' ||
42 ch
== '\\' || ch
== '&' || ch
== '<' ||
43 ch
== '>' || ch
== '#' || ch
== '{' ||
44 ch
== '}' || ch
== '[' || ch
== ']');
47 static inline bool IsANumberChar(int ch
) {
48 // Not exactly following number definition (several dots are seen as OK, etc.)
49 // but probably enough in most cases.
51 (isdigit(ch
) || toupper(ch
) == 'E' ||
52 ch
== '.' || ch
== '-' || ch
== '+');
55 //--------------------------------------------------------------------------------------------------
58 * Check if the current content context represent a keyword and set the context state if so.
60 static void CheckForKeyword(StyleContext
& sc
, WordList
* keywordlists
[])
62 int length
= sc
.LengthCurrent() + 1; // +1 for the next char
63 char* s
= new char[length
];
64 sc
.GetCurrentLowered(s
, length
);
65 if (keywordlists
[0]->InList(s
))
66 sc
.ChangeState(SCE_MYSQL_MAJORKEYWORD
);
68 if (keywordlists
[1]->InList(s
))
69 sc
.ChangeState(SCE_MYSQL_KEYWORD
);
71 if (keywordlists
[2]->InList(s
))
72 sc
.ChangeState(SCE_MYSQL_DATABASEOBJECT
);
74 if (keywordlists
[3]->InList(s
))
75 sc
.ChangeState(SCE_MYSQL_FUNCTION
);
77 if (keywordlists
[5]->InList(s
))
78 sc
.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD
);
80 if (keywordlists
[6]->InList(s
))
81 sc
.ChangeState(SCE_MYSQL_USER1
);
83 if (keywordlists
[7]->InList(s
))
84 sc
.ChangeState(SCE_MYSQL_USER2
);
86 if (keywordlists
[8]->InList(s
))
87 sc
.ChangeState(SCE_MYSQL_USER3
);
91 //--------------------------------------------------------------------------------------------------
93 static void ColouriseMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*keywordlists
[],
96 StyleContext
sc(startPos
, length
, initStyle
, styler
);
98 for (; sc
.More(); sc
.Forward())
100 // Determine if the current state should terminate.
103 case SCE_MYSQL_OPERATOR
:
104 sc
.SetState(SCE_MYSQL_DEFAULT
);
106 case SCE_MYSQL_NUMBER
:
107 // We stop the number definition on non-numerical non-dot non-eE non-sign char.
108 if (!IsANumberChar(sc
.ch
))
109 sc
.SetState(SCE_MYSQL_DEFAULT
);
111 case SCE_MYSQL_IDENTIFIER
:
112 // Switch from identifier to keyword state and open a new state for the new char.
113 if (!IsAWordChar(sc
.ch
))
115 CheckForKeyword(sc
, keywordlists
);
117 // Additional check for function keywords needed.
118 // A function name must be followed by an opening parenthesis.
119 if (sc
.state
== SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
120 sc
.ChangeState(SCE_MYSQL_DEFAULT
);
122 sc
.SetState(SCE_MYSQL_DEFAULT
);
125 case SCE_MYSQL_VARIABLE
:
126 if (!IsAWordChar(sc
.ch
))
127 sc
.SetState(SCE_MYSQL_DEFAULT
);
129 case SCE_MYSQL_SYSTEMVARIABLE
:
130 if (!IsAWordChar(sc
.ch
))
132 int length
= sc
.LengthCurrent() + 1;
133 char* s
= new char[length
];
134 sc
.GetCurrentLowered(s
, length
);
136 // Check for known system variables here.
137 if (keywordlists
[4]->InList(&s
[2]))
138 sc
.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE
);
141 sc
.SetState(SCE_MYSQL_DEFAULT
);
144 case SCE_MYSQL_QUOTEDIDENTIFIER
:
147 if (sc
.chNext
== '`')
148 sc
.Forward(); // Ignore it
150 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
153 case SCE_MYSQL_COMMENT
:
154 case SCE_MYSQL_HIDDENCOMMAND
:
155 if (sc
.Match('*', '/'))
158 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
161 case SCE_MYSQL_COMMENTLINE
:
163 sc
.SetState(SCE_MYSQL_DEFAULT
);
165 case SCE_MYSQL_SQSTRING
:
167 sc
.Forward(); // Escape sequence
171 // End of single quoted string reached?
172 if (sc
.chNext
== '\'')
175 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
178 case SCE_MYSQL_DQSTRING
:
180 sc
.Forward(); // Escape sequence
184 // End of single quoted string reached?
185 if (sc
.chNext
== '\"')
188 sc
.ForwardSetState(SCE_MYSQL_DEFAULT
);
193 // Determine if a new state should be entered.
194 if (sc
.state
== SCE_MYSQL_DEFAULT
)
199 if (sc
.chNext
== '@')
201 sc
.SetState(SCE_MYSQL_SYSTEMVARIABLE
);
202 sc
.Forward(2); // Skip past @@.
205 if (IsAWordStart(sc
.ch
))
207 sc
.SetState(SCE_MYSQL_VARIABLE
);
208 sc
.Forward(); // Skip past @.
211 sc
.SetState(SCE_MYSQL_OPERATOR
);
214 sc
.SetState(SCE_MYSQL_QUOTEDIDENTIFIER
);
217 sc
.SetState(SCE_MYSQL_COMMENTLINE
);
220 sc
.SetState(SCE_MYSQL_SQSTRING
);
223 sc
.SetState(SCE_MYSQL_DQSTRING
);
226 if (IsADigit(sc
.ch
) || (sc
.ch
== '.' && IsADigit(sc
.chNext
)))
227 sc
.SetState(SCE_MYSQL_NUMBER
);
229 if (IsAWordStart(sc
.ch
))
230 sc
.SetState(SCE_MYSQL_IDENTIFIER
);
232 if (sc
.Match('/', '*'))
234 sc
.SetState(SCE_MYSQL_COMMENT
);
236 // Skip comment introducer and check for hidden command.
240 sc
.ChangeState(SCE_MYSQL_HIDDENCOMMAND
);
247 // Special MySQL single line comment.
248 sc
.SetState(SCE_MYSQL_COMMENTLINE
);
251 // Check the third character too. It must be a space or EOL.
252 if (sc
.ch
!= ' ' && sc
.ch
!= '\n' && sc
.ch
!= '\r')
253 sc
.ChangeState(SCE_MYSQL_OPERATOR
);
256 if (isoperator(static_cast<char>(sc
.ch
)))
257 sc
.SetState(SCE_MYSQL_OPERATOR
);
262 // Do a final check for keywords if we currently have an identifier, to highlight them
263 // also at the end of a line.
264 if (sc
.state
== SCE_MYSQL_IDENTIFIER
)
266 CheckForKeyword(sc
, keywordlists
);
268 // Additional check for function keywords needed.
269 // A function name must be followed by an opening parenthesis.
270 if (sc
.state
== SCE_MYSQL_FUNCTION
&& sc
.ch
!= '(')
271 sc
.ChangeState(SCE_MYSQL_DEFAULT
);
277 //--------------------------------------------------------------------------------------------------
280 * Helper function to determine if we have a foldable comment currently.
282 static bool IsStreamCommentStyle(int style
)
284 return style
== SCE_MYSQL_COMMENT
;
287 //--------------------------------------------------------------------------------------------------
290 * Code copied from StyleContext and modified to work here. Should go into Accessor as a
291 * companion to Match()...
293 bool MatchIgnoreCase(Accessor
&styler
, int currentPos
, const char *s
)
295 for (int n
= 0; *s
; n
++)
297 if (*s
!= tolower(styler
.SafeGetCharAt(currentPos
+ n
)))
304 //--------------------------------------------------------------------------------------------------
306 // Store both the current line's fold level and the next lines in the
307 // level store to make it easy to pick up with each increment.
308 static void FoldMySQLDoc(unsigned int startPos
, int length
, int initStyle
, WordList
*[], Accessor
&styler
)
310 bool foldComment
= styler
.GetPropertyInt("fold.comment") != 0;
311 bool foldCompact
= styler
.GetPropertyInt("fold.compact", 1) != 0;
312 bool foldOnlyBegin
= styler
.GetPropertyInt("fold.sql.only.begin", 0) != 0;
314 int visibleChars
= 0;
315 int lineCurrent
= styler
.GetLine(startPos
);
316 int levelCurrent
= SC_FOLDLEVELBASE
;
318 levelCurrent
= styler
.LevelAt(lineCurrent
- 1) >> 16;
319 int levelNext
= levelCurrent
;
321 int styleNext
= styler
.StyleAt(startPos
);
322 int style
= initStyle
;
324 bool endFound
= false;
325 bool whenFound
= false;
326 bool elseFound
= false;
328 char nextChar
= styler
.SafeGetCharAt(startPos
);
329 for (unsigned int i
= startPos
; length
> 0; i
++, length
--)
331 int stylePrev
= style
;
333 styleNext
= styler
.StyleAt(i
+ 1);
335 char currentChar
= nextChar
;
336 nextChar
= styler
.SafeGetCharAt(i
+ 1);
337 bool atEOL
= (currentChar
== '\r' && nextChar
!= '\n') || (currentChar
== '\n');
341 case SCE_MYSQL_COMMENT
:
344 // Multiline comment style /* .. */.
345 if (IsStreamCommentStyle(style
))
347 // Increase level if we just start a foldable comment.
348 if (!IsStreamCommentStyle(stylePrev
))
351 // If we are in the middle of a foldable comment check if it ends now.
352 // Don't end at the line end, though.
353 if (!IsStreamCommentStyle(styleNext
) && !atEOL
)
358 case SCE_MYSQL_COMMENTLINE
:
361 // Not really a standard, but we add support for single line comments
362 // with special curly braces syntax as foldable comments too.
363 // MySQL needs -- comments to be followed by space or control char
364 if (styler
.Match(i
, "--"))
366 char chNext2
= styler
.SafeGetCharAt(i
+ 2);
367 char chNext3
= styler
.SafeGetCharAt(i
+ 3);
368 if (chNext2
== '{' || chNext3
== '{')
371 if (chNext2
== '}' || chNext3
== '}')
376 case SCE_MYSQL_HIDDENCOMMAND
:
377 if (style
!= stylePrev
)
380 if (style
!= styleNext
)
383 case SCE_MYSQL_OPERATOR
:
384 if (currentChar
== '(')
387 if (currentChar
== ')')
390 case SCE_MYSQL_MAJORKEYWORD
:
391 case SCE_MYSQL_KEYWORD
:
392 case SCE_MYSQL_FUNCTION
:
393 case SCE_MYSQL_PROCEDUREKEYWORD
:
394 // Reserved and other keywords.
395 if (style
!= stylePrev
)
397 bool beginFound
= MatchIgnoreCase(styler
, i
, "begin");
398 bool ifFound
= MatchIgnoreCase(styler
, i
, "if");
399 bool thenFound
= MatchIgnoreCase(styler
, i
, "then");
400 bool whileFound
= MatchIgnoreCase(styler
, i
, "while");
401 bool loopFound
= MatchIgnoreCase(styler
, i
, "loop");
402 bool repeatFound
= MatchIgnoreCase(styler
, i
, "repeat");
404 if (!foldOnlyBegin
&& endFound
&& (ifFound
|| whileFound
|| loopFound
))
408 if (levelNext
< SC_FOLDLEVELBASE
)
409 levelNext
= SC_FOLDLEVELBASE
;
411 // Note that "else" is special here. It may or may not be followed by an "if .. then",
412 // but in any case the level stays the same. When followed by an "if .. then" the level
413 // will be increased later, if not, then at eol.
416 if (!foldOnlyBegin
&& MatchIgnoreCase(styler
, i
, "else"))
422 if (!foldOnlyBegin
&& thenFound
)
433 if (MatchIgnoreCase(styler
, i
, "when"))
440 if (!foldOnlyBegin
&& (loopFound
|| repeatFound
|| whileFound
))
448 if (MatchIgnoreCase(styler
, i
, "end"))
450 // Multiple "end" in a row are counted multiple times!
454 if (levelNext
< SC_FOLDLEVELBASE
)
455 levelNext
= SC_FOLDLEVELBASE
;
465 // Handle the case of a trailing end without an if / while etc, as in the case of a begin.
470 if (levelNext
< SC_FOLDLEVELBASE
)
471 levelNext
= SC_FOLDLEVELBASE
;
482 int levelUse
= levelCurrent
;
483 int lev
= levelUse
| levelNext
<< 16;
484 if (visibleChars
== 0 && foldCompact
)
485 lev
|= SC_FOLDLEVELWHITEFLAG
;
486 if (levelUse
< levelNext
)
487 lev
|= SC_FOLDLEVELHEADERFLAG
;
488 if (lev
!= styler
.LevelAt(lineCurrent
))
489 styler
.SetLevel(lineCurrent
, lev
);
492 levelCurrent
= levelNext
;
498 if (!isspacechar(currentChar
))
503 //--------------------------------------------------------------------------------------------------
505 static const char * const mysqlWordListDesc
[] = {
511 "Procedure keywords",
518 LexerModule
lmMySQL(SCLEX_MYSQL
, ColouriseMySQLDoc
, "mysql", FoldMySQLDoc
, mysqlWordListDesc
);