1 // Scintilla source code edit control
\r
2 /** @file LexMySQL.cxx
\r
5 // Adopted from LexSQL.cxx by Anders Karlsson <anders@mysql.com>
\r
6 // Original work by Neil Hodgson <neilh@scintilla.org>
\r
7 // Copyright 1998-2005 by Neil Hodgson <neilh@scintilla.org>
\r
8 // The License.txt file describes the conditions under which this software may be distributed.
\r
16 #include "Platform.h"
\r
18 #include "PropSet.h"
\r
19 #include "Accessor.h"
\r
20 #include "StyleContext.h"
\r
21 #include "KeyWords.h"
\r
22 #include "Scintilla.h"
\r
23 #include "SciLexer.h"
\r
25 #ifdef SCI_NAMESPACE
\r
26 using namespace Scintilla;
\r
29 static inline bool IsAWordChar(int ch) {
\r
30 return (ch < 0x80) && (isalnum(ch) || ch == '_');
\r
33 static inline bool IsAWordStart(int ch) {
\r
34 return (ch < 0x80) && (isalpha(ch) || ch == '_');
\r
37 static inline bool IsADoxygenChar(int ch) {
\r
38 return (islower(ch) || ch == '$' || ch == '@' ||
\r
39 ch == '\\' || ch == '&' || ch == '<' ||
\r
40 ch == '>' || ch == '#' || ch == '{' ||
\r
41 ch == '}' || ch == '[' || ch == ']');
\r
44 static inline bool IsANumberChar(int ch) {
\r
45 // Not exactly following number definition (several dots are seen as OK, etc.)
\r
46 // but probably enough in most cases.
\r
47 return (ch < 0x80) &&
\r
48 (isdigit(ch) || toupper(ch) == 'E' ||
\r
49 ch == '.' || ch == '-' || ch == '+');
\r
52 static void ColouriseMySQLDoc(unsigned int startPos, int length, int initStyle, WordList *keywordlists[],
\r
55 WordList &major_keywords = *keywordlists[0];
\r
56 WordList &keywords = *keywordlists[1];
\r
57 WordList &database_objects = *keywordlists[2];
\r
58 WordList &functions = *keywordlists[3];
\r
59 WordList &system_variables = *keywordlists[4];
\r
60 WordList &procedure_keywords = *keywordlists[5];
\r
61 WordList &kw_user1 = *keywordlists[6];
\r
62 WordList &kw_user2 = *keywordlists[7];
\r
63 WordList &kw_user3 = *keywordlists[8];
\r
65 StyleContext sc(startPos, length, initStyle, styler);
\r
67 for (; sc.More(); sc.Forward()) {
\r
68 // Determine if the current state should terminate.
\r
70 case SCE_MYSQL_OPERATOR:
\r
71 sc.SetState(SCE_MYSQL_DEFAULT);
\r
73 case SCE_MYSQL_NUMBER:
\r
74 // We stop the number definition on non-numerical non-dot non-eE non-sign char
\r
75 if (!IsANumberChar(sc.ch)) {
\r
76 sc.SetState(SCE_MYSQL_DEFAULT);
\r
79 case SCE_MYSQL_IDENTIFIER:
\r
80 if (!IsAWordChar(sc.ch)) {
\r
81 int nextState = SCE_MYSQL_DEFAULT;
\r
83 sc.GetCurrentLowered(s, sizeof(s));
\r
84 if (major_keywords.InList(s)) {
\r
85 sc.ChangeState(SCE_MYSQL_MAJORKEYWORD);
\r
86 } else if (keywords.InList(s)) {
\r
87 sc.ChangeState(SCE_MYSQL_KEYWORD);
\r
88 } else if (database_objects.InList(s)) {
\r
89 sc.ChangeState(SCE_MYSQL_DATABASEOBJECT);
\r
90 } else if (functions.InList(s)) {
\r
91 sc.ChangeState(SCE_MYSQL_FUNCTION);
\r
92 } else if (procedure_keywords.InList(s)) {
\r
93 sc.ChangeState(SCE_MYSQL_PROCEDUREKEYWORD);
\r
94 } else if (kw_user1.InList(s)) {
\r
95 sc.ChangeState(SCE_MYSQL_USER1);
\r
96 } else if (kw_user2.InList(s)) {
\r
97 sc.ChangeState(SCE_MYSQL_USER2);
\r
98 } else if (kw_user3.InList(s)) {
\r
99 sc.ChangeState(SCE_MYSQL_USER3);
\r
101 sc.SetState(nextState);
\r
104 case SCE_MYSQL_VARIABLE:
\r
105 if (!IsAWordChar(sc.ch)) {
\r
106 sc.SetState(SCE_MYSQL_DEFAULT);
\r
109 case SCE_MYSQL_SYSTEMVARIABLE:
\r
110 if (!IsAWordChar(sc.ch)) {
\r
112 sc.GetCurrentLowered(s, sizeof(s));
\r
113 // Check for known system variables here.
\r
114 if (system_variables.InList(&s[2])) {
\r
115 sc.ChangeState(SCE_MYSQL_KNOWNSYSTEMVARIABLE);
\r
117 sc.SetState(SCE_MYSQL_DEFAULT);
\r
120 case SCE_MYSQL_QUOTEDIDENTIFIER:
\r
121 if (sc.ch == 0x60) {
\r
122 if (sc.chNext == 0x60) {
\r
123 sc.Forward(); // Ignore it
\r
125 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
\r
129 case SCE_MYSQL_COMMENT:
\r
130 if (sc.Match('*', '/')) {
\r
132 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
\r
135 case SCE_MYSQL_COMMENTLINE:
\r
136 if (sc.atLineStart) {
\r
137 sc.SetState(SCE_MYSQL_DEFAULT);
\r
140 case SCE_MYSQL_SQSTRING:
\r
141 if (sc.ch == '\\') {
\r
144 } else if (sc.ch == '\'') {
\r
145 if (sc.chNext == '\'') {
\r
148 sc.ChangeState(SCE_MYSQL_STRING);
\r
149 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
\r
153 case SCE_MYSQL_DQSTRING:
\r
154 if (sc.ch == '\\') {
\r
157 } else if (sc.ch == '\"') {
\r
158 if (sc.chNext == '\"') {
\r
161 sc.ChangeState(SCE_MYSQL_STRING);
\r
162 sc.ForwardSetState(SCE_MYSQL_DEFAULT);
\r
168 // Determine if a new state should be entered.
\r
169 if (sc.state == SCE_MYSQL_DEFAULT) {
\r
170 if (IsADigit(sc.ch) || (sc.ch == '.' && IsADigit(sc.chNext))) {
\r
171 sc.SetState(SCE_MYSQL_NUMBER);
\r
172 } else if (IsAWordStart(sc.ch)) {
\r
173 sc.SetState(SCE_MYSQL_IDENTIFIER);
\r
174 // Note that the order of SYSTEMVARIABLE and VARIABLE is important here.
\r
175 } else if (sc.ch == 0x40 && sc.chNext == 0x40) {
\r
176 sc.SetState(SCE_MYSQL_SYSTEMVARIABLE);
\r
177 sc.Forward(); // Skip past the second at-sign.
\r
178 } else if (sc.ch == 0x40) {
\r
179 sc.SetState(SCE_MYSQL_VARIABLE);
\r
180 } else if (sc.ch == 0x60) {
\r
181 sc.SetState(SCE_MYSQL_QUOTEDIDENTIFIER);
\r
182 } else if (sc.Match('/', '*')) {
\r
183 sc.SetState(SCE_MYSQL_COMMENT);
\r
184 sc.Forward(); // Eat the * so it isn't used for the end of the comment
\r
185 } else if (sc.Match('-', '-') || sc.Match('#')) {
\r
186 sc.SetState(SCE_MYSQL_COMMENTLINE);
\r
187 } else if (sc.ch == '\'') {
\r
188 sc.SetState(SCE_MYSQL_SQSTRING);
\r
189 } else if (sc.ch == '\"') {
\r
190 sc.SetState(SCE_MYSQL_DQSTRING);
\r
191 } else if (isoperator(static_cast<char>(sc.ch))) {
\r
192 sc.SetState(SCE_MYSQL_OPERATOR);
\r
199 static bool IsStreamCommentStyle(int style) {
\r
200 return style == SCE_MYSQL_COMMENT;
\r
203 // Store both the current line's fold level and the next lines in the
\r
204 // level store to make it easy to pick up with each increment.
\r
205 static void FoldMySQLDoc(unsigned int startPos, int length, int initStyle,
\r
206 WordList *[], Accessor &styler) {
\r
207 bool foldComment = styler.GetPropertyInt("fold.comment") != 0;
\r
208 bool foldCompact = styler.GetPropertyInt("fold.compact", 1) != 0;
\r
209 bool foldOnlyBegin = styler.GetPropertyInt("fold.sql.only.begin", 0) != 0;
\r
211 unsigned int endPos = startPos + length;
\r
212 int visibleChars = 0;
\r
213 int lineCurrent = styler.GetLine(startPos);
\r
214 int levelCurrent = SC_FOLDLEVELBASE;
\r
215 if (lineCurrent > 0) {
\r
216 levelCurrent = styler.LevelAt(lineCurrent - 1) >> 16;
\r
218 int levelNext = levelCurrent;
\r
219 char chNext = styler[startPos];
\r
220 int styleNext = styler.StyleAt(startPos);
\r
221 int style = initStyle;
\r
222 bool endFound = false;
\r
223 bool whenFound = false;
\r
224 bool elseFound = false;
\r
225 for (unsigned int i = startPos; i < endPos; i++) {
\r
227 chNext = styler.SafeGetCharAt(i + 1);
\r
228 int stylePrev = style;
\r
230 styleNext = styler.StyleAt(i + 1);
\r
231 bool atEOL = (ch == '\r' && chNext != '\n') || (ch == '\n');
\r
232 if (foldComment && IsStreamCommentStyle(style)) {
\r
233 if (!IsStreamCommentStyle(stylePrev)) {
\r
235 } else if (!IsStreamCommentStyle(styleNext) && !atEOL) {
\r
236 // Comments don't end at end of line and the next character may be unstyled.
\r
240 if (foldComment && (style == SCE_MYSQL_COMMENTLINE)) {
\r
241 // MySQL needs -- comments to be followed by space or control char
\r
242 if ((ch == '-') && (chNext == '-')) {
\r
243 char chNext2 = styler.SafeGetCharAt(i + 2);
\r
244 char chNext3 = styler.SafeGetCharAt(i + 3);
\r
245 if (chNext2 == '{' || chNext3 == '{') {
\r
247 } else if (chNext2 == '}' || chNext3 == '}') {
\r
252 if (style == SCE_MYSQL_OPERATOR) {
\r
255 } else if (ch == ')') {
\r
260 // Style new keywords here.
\r
261 if ((style == SCE_MYSQL_MAJORKEYWORD && stylePrev != SCE_MYSQL_MAJORKEYWORD)
\r
262 || (style == SCE_MYSQL_KEYWORD && stylePrev != SCE_MYSQL_KEYWORD)
\r
263 || (style == SCE_MYSQL_PROCEDUREKEYWORD && stylePrev != SCE_MYSQL_PROCEDUREKEYWORD)) {
\r
264 const int MAX_KW_LEN = 6; // Maximum length of folding keywords
\r
265 char s[MAX_KW_LEN + 2];
\r
266 unsigned int j = 0;
\r
267 for (; j < MAX_KW_LEN + 1; j++) {
\r
268 if (!iswordchar(styler[i + j])) {
\r
271 s[j] = static_cast<char>(tolower(styler[i + j]));
\r
273 if (j == MAX_KW_LEN + 1) {
\r
274 // Keyword too long, don't test it
\r
279 if (!foldOnlyBegin && endFound && (strcmp(s, "if") == 0 || strcmp(s, "while") == 0 || strcmp(s, "loop") == 0)) {
\r
282 if (levelNext < SC_FOLDLEVELBASE) {
\r
283 levelNext = SC_FOLDLEVELBASE;
\r
285 // Note that else is special here. It may or may be followed by an if then, but in aly case the level stays the
\r
286 // same. When followed by a if .. then, the level will be increased later, if not, at eol.
\r
287 } else if (!foldOnlyBegin && strcmp(s, "else") == 0) {
\r
290 } else if (!foldOnlyBegin && strcmp(s, "then") == 0) {
\r
296 } else if (strcmp(s, "if") == 0) {
\r
298 } else if (strcmp(s, "when") == 0) {
\r
300 } else if (strcmp(s, "begin") == 0) {
\r
302 } else if (!foldOnlyBegin && (strcmp(s, "loop") == 0 || strcmp(s, "repeat") == 0
\r
303 || strcmp(s, "while") == 0)) {
\r
309 } else if (strcmp(s, "end") == 0) {
\r
310 // Multiple END in a row are counted multiple times!
\r
313 if (levelNext < SC_FOLDLEVELBASE) {
\r
314 levelNext = SC_FOLDLEVELBASE;
\r
321 // Handle this for a trailing end withiut an if / while etc, as in the case of a begin.
\r
325 if (levelNext < SC_FOLDLEVELBASE) {
\r
326 levelNext = SC_FOLDLEVELBASE;
\r
334 int levelUse = levelCurrent;
\r
335 int lev = levelUse | levelNext << 16;
\r
336 if (visibleChars == 0 && foldCompact)
\r
337 lev |= SC_FOLDLEVELWHITEFLAG;
\r
338 if (levelUse < levelNext)
\r
339 lev |= SC_FOLDLEVELHEADERFLAG;
\r
340 if (lev != styler.LevelAt(lineCurrent)) {
\r
341 styler.SetLevel(lineCurrent, lev);
\r
344 levelCurrent = levelNext;
\r
349 if (!isspacechar(ch)) {
\r
355 static const char * const mysqlWordListDesc[] = {
\r
358 "Database Objects",
\r
360 "System Variables",
\r
361 "Procedure keywords",
\r
367 LexerModule lmMySQL(SCLEX_MYSQL, ColouriseMySQLDoc, "mysql", FoldMySQLDoc, mysqlWordListDesc);
\r