1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2018 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtSCriptTools module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:LGPL$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU Lesser General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU Lesser |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 3 requirements |
23 | ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
24 | ** |
25 | ** GNU General Public License Usage |
26 | ** Alternatively, this file may be used under the terms of the GNU |
27 | ** General Public License version 2.0 or (at your option) the GNU General |
28 | ** Public license version 3 or any later version approved by the KDE Free |
29 | ** Qt Foundation. The licenses are as published by the Free Software |
30 | ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
31 | ** included in the packaging of this file. Please review the following |
32 | ** information to ensure the GNU General Public License requirements will |
33 | ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
34 | ** https://www.gnu.org/licenses/gpl-3.0.html. |
35 | ** |
36 | ** $QT_END_LICENSE$ |
37 | ** |
38 | ****************************************************************************/ |
39 | |
40 | #include "qscriptsyntaxhighlighter_p.h" |
41 | |
42 | #include <algorithm> |
43 | |
44 | #ifndef QT_NO_SYNTAXHIGHLIGHTER |
45 | |
46 | QT_BEGIN_NAMESPACE |
47 | |
48 | enum ScriptIds { |
49 | = 1, |
50 | Number, |
51 | String, |
52 | Type, |
53 | Keyword, |
54 | PreProcessor, |
55 | Label |
56 | }; |
57 | |
58 | #define MAX_KEYWORD 63 |
59 | static const char *const keywords[MAX_KEYWORD] = { |
60 | "Infinity" , |
61 | "NaN" , |
62 | "abstract" , |
63 | "boolean" , |
64 | "break" , |
65 | "byte" , |
66 | "case" , |
67 | "catch" , |
68 | "char" , |
69 | "class" , |
70 | "const" , |
71 | "constructor" , |
72 | "continue" , |
73 | "debugger" , |
74 | "default" , |
75 | "delete" , |
76 | "do" , |
77 | "double" , |
78 | "else" , |
79 | "enum" , |
80 | "export" , |
81 | "extends" , |
82 | "false" , |
83 | "final" , |
84 | "finally" , |
85 | "float" , |
86 | "for" , |
87 | "function" , |
88 | "goto" , |
89 | "if" , |
90 | "implements" , |
91 | "import" , |
92 | "in" , |
93 | "instanceof" , |
94 | "int" , |
95 | "interface" , |
96 | "long" , |
97 | "native" , |
98 | "new" , |
99 | "package" , |
100 | "private" , |
101 | "protected" , |
102 | "public" , |
103 | "return" , |
104 | "short" , |
105 | "static" , |
106 | "super" , |
107 | "switch" , |
108 | "synchronized" , |
109 | "this" , |
110 | "throw" , |
111 | "throws" , |
112 | "transient" , |
113 | "true" , |
114 | "try" , |
115 | "typeof" , |
116 | "undefined" , |
117 | "var" , |
118 | "void" , |
119 | "volatile" , |
120 | "while" , |
121 | "with" , // end of array |
122 | 0 |
123 | }; |
124 | |
125 | struct KeywordHelper |
126 | { |
127 | inline KeywordHelper(const QString &word) : needle(word) {} |
128 | const QString needle; |
129 | }; |
130 | |
131 | static bool operator<(const KeywordHelper &helper, const char *kw) |
132 | { |
133 | return helper.needle < QLatin1String(kw); |
134 | } |
135 | |
136 | static bool operator<(const char *kw, const KeywordHelper &helper) |
137 | { |
138 | return QLatin1String(kw) < helper.needle; |
139 | } |
140 | |
141 | static bool isKeyword(const QString &word) |
142 | { |
143 | const char * const *start = &keywords[0]; |
144 | const char * const *end = &keywords[MAX_KEYWORD - 1]; |
145 | |
146 | const KeywordHelper keywordHelper(word); |
147 | const char * const *kw = std::lower_bound(first: start, last: end, val: keywordHelper); |
148 | |
149 | return kw != end && !(keywordHelper < *kw); |
150 | } |
151 | |
152 | QScriptSyntaxHighlighter::QScriptSyntaxHighlighter(QTextDocument *document) |
153 | : QSyntaxHighlighter(document) |
154 | { |
155 | |
156 | m_formats[ScriptNumberFormat].setForeground(Qt::darkBlue); |
157 | m_formats[ScriptStringFormat].setForeground(Qt::darkGreen); |
158 | m_formats[ScriptTypeFormat].setForeground(Qt::darkMagenta); |
159 | m_formats[ScriptKeywordFormat].setForeground(Qt::darkYellow); |
160 | m_formats[ScriptPreprocessorFormat].setForeground(Qt::darkBlue); |
161 | m_formats[ScriptLabelFormat].setForeground(Qt::darkRed); |
162 | m_formats[ScriptCommentFormat].setForeground(Qt::darkGreen); |
163 | m_formats[ScriptCommentFormat].setFontItalic(true); |
164 | } |
165 | |
166 | QScriptSyntaxHighlighter::~QScriptSyntaxHighlighter() |
167 | { |
168 | } |
169 | |
170 | void QScriptSyntaxHighlighter::highlightBlock(const QString &text) |
171 | { |
172 | |
173 | // states |
174 | enum States { StateStandard, , , |
175 | , , , , |
176 | , StateStringStart, StateString, StateStringEnd, |
177 | StateString2Start, StateString2, StateString2End, |
178 | StateNumber, StatePreProcessor, NumStates }; |
179 | |
180 | // tokens |
181 | enum Tokens { InputAlpha, InputNumber, InputAsterix, InputSlash, InputParen, |
182 | InputSpace, InputHash, InputQuotation, InputApostrophe, InputSep, NumTokens }; |
183 | |
184 | static uchar table[NumStates][NumTokens] = { |
185 | { StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStandard |
186 | { StateStandard, StateNumber, StateCCommentStart2, StateScriptCommentStart2, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCommentStart1 |
187 | { StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentStart2 |
188 | { StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // ScriptCommentStart2 |
189 | { StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCComment |
190 | { StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // StateScriptComment |
191 | { StateCComment, StateCComment, StateCCommentEnd1, StateCCommentEnd2, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentEnd1 |
192 | { StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCCommentEnd2 |
193 | { StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateStringStart |
194 | { StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateString |
195 | { StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStringEnd |
196 | { StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2Start |
197 | { StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2 |
198 | { StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateString2End |
199 | { StateNumber, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateNumber |
200 | { StatePreProcessor, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard } // StatePreProcessor |
201 | }; |
202 | |
203 | QString buffer; |
204 | buffer.reserve(asize: text.length()); |
205 | |
206 | QTextCharFormat emptyFormat; |
207 | |
208 | int state = StateStandard; |
209 | int braceDepth = 0; |
210 | const int previousState = previousBlockState(); |
211 | if (previousState != -1) { |
212 | state = previousState & 0xff; |
213 | braceDepth = previousState >> 8; |
214 | } |
215 | |
216 | if (text.isEmpty()) { |
217 | setCurrentBlockState(previousState); |
218 | #if 0 |
219 | TextEditDocumentLayout::clearParentheses(currentBlock()); |
220 | #endif |
221 | return; |
222 | } |
223 | #if 0 |
224 | Parentheses parentheses; |
225 | parentheses.reserve(20); // assume wizard level ;-) |
226 | #endif |
227 | int input = -1; |
228 | int i = 0; |
229 | bool lastWasBackSlash = false; |
230 | bool makeLastStandard = false; |
231 | |
232 | static const QString alphabeth = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" ); |
233 | static const QString mathChars = QLatin1String("xXeE" ); |
234 | static const QString numbers = QLatin1String("0123456789" ); |
235 | bool questionMark = false; |
236 | QChar lastChar; |
237 | |
238 | int firstNonSpace = -1; |
239 | |
240 | for (;;) { |
241 | const QChar c = text.at(i); |
242 | |
243 | if (lastWasBackSlash) { |
244 | input = InputSep; |
245 | } else { |
246 | switch (c.toLatin1()) { |
247 | case '*': |
248 | input = InputAsterix; |
249 | break; |
250 | case '/': |
251 | input = InputSlash; |
252 | break; |
253 | case '{': |
254 | braceDepth++; |
255 | // fall through |
256 | case '(': case '[': |
257 | input = InputParen; |
258 | switch (state) { |
259 | case StateStandard: |
260 | case StateNumber: |
261 | case StatePreProcessor: |
262 | case StateCCommentEnd2: |
263 | case StateCCommentEnd1: |
264 | case StateString2End: |
265 | case StateStringEnd: |
266 | // parentheses.push_back(Parenthesis(Parenthesis::Opened, c, i)); |
267 | break; |
268 | default: |
269 | break; |
270 | } |
271 | break; |
272 | case '}': |
273 | if (--braceDepth < 0) |
274 | braceDepth = 0; |
275 | // fall through |
276 | case ')': case ']': |
277 | input = InputParen; |
278 | switch (state) { |
279 | case StateStandard: |
280 | case StateNumber: |
281 | case StatePreProcessor: |
282 | case StateCCommentEnd2: |
283 | case StateCCommentEnd1: |
284 | case StateString2End: |
285 | case StateStringEnd: |
286 | // parentheses.push_back(Parenthesis(Parenthesis::Closed, c, i)); |
287 | break; |
288 | default: |
289 | break; |
290 | } |
291 | break; |
292 | case '#': |
293 | input = InputHash; |
294 | break; |
295 | case '"': |
296 | input = InputQuotation; |
297 | break; |
298 | case '\'': |
299 | input = InputApostrophe; |
300 | break; |
301 | case ' ': |
302 | input = InputSpace; |
303 | break; |
304 | case '1': case '2': case '3': case '4': case '5': |
305 | case '6': case '7': case '8': case '9': case '0': |
306 | if (alphabeth.contains(c: lastChar) |
307 | && (!mathChars.contains(c: lastChar) || !numbers.contains(c: text.at(i: i - 1))) |
308 | ) { |
309 | input = InputAlpha; |
310 | } else { |
311 | if (input == InputAlpha && numbers.contains(c: lastChar)) |
312 | input = InputAlpha; |
313 | else |
314 | input = InputNumber; |
315 | } |
316 | break; |
317 | case ':': { |
318 | input = InputAlpha; |
319 | const QChar colon = QLatin1Char(':'); |
320 | if (state == StateStandard && !questionMark && lastChar != colon) { |
321 | const QChar nextChar = i < text.length() - 1 ? text.at(i: i + 1) : QLatin1Char(' '); |
322 | if (nextChar != colon) |
323 | for (int j = 0; j < i; ++j) { |
324 | if (format(pos: j) == emptyFormat ) |
325 | setFormat(start: j, count: 1, format: m_formats[ScriptLabelFormat]); |
326 | } |
327 | } |
328 | } break; |
329 | default: |
330 | if (!questionMark && c == QLatin1Char('?')) |
331 | questionMark = true; |
332 | if (c.isLetter() || c == QLatin1Char('_')) |
333 | input = InputAlpha; |
334 | else |
335 | input = InputSep; |
336 | break; |
337 | } |
338 | } |
339 | |
340 | if (input != InputSpace) { |
341 | if (firstNonSpace < 0) |
342 | firstNonSpace = i; |
343 | } |
344 | |
345 | lastWasBackSlash = !lastWasBackSlash && c == QLatin1Char('\\'); |
346 | |
347 | if (input == InputAlpha) |
348 | buffer += c; |
349 | |
350 | state = table[state][input]; |
351 | |
352 | switch (state) { |
353 | case StateStandard: { |
354 | setFormat(start: i, count: 1, format: emptyFormat); |
355 | if (makeLastStandard) |
356 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
357 | makeLastStandard = false; |
358 | if (input != InputAlpha) { |
359 | highlightWord(currentPos: i, buffer); |
360 | buffer = QString(); |
361 | } |
362 | } break; |
363 | case StateCommentStart1: |
364 | if (makeLastStandard) |
365 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
366 | makeLastStandard = true; |
367 | buffer = QString(); |
368 | break; |
369 | case StateCCommentStart2: |
370 | setFormat(start: i - 1, count: 2, format: m_formats[ScriptCommentFormat]); |
371 | makeLastStandard = false; |
372 | // parentheses.push_back(Parenthesis(Parenthesis::Opened, QLatin1Char('/'), i-1)); |
373 | buffer = QString(); |
374 | break; |
375 | case StateScriptCommentStart2: |
376 | setFormat(start: i - 1, count: 2, format: m_formats[ScriptCommentFormat]); |
377 | makeLastStandard = false; |
378 | buffer = QString(); |
379 | break; |
380 | case StateCComment: |
381 | if (makeLastStandard) |
382 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
383 | makeLastStandard = false; |
384 | setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]); |
385 | buffer = QString(); |
386 | break; |
387 | case StateScriptComment: |
388 | if (makeLastStandard) |
389 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
390 | makeLastStandard = false; |
391 | setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]); |
392 | buffer = QString(); |
393 | break; |
394 | case StateCCommentEnd1: |
395 | if (makeLastStandard) |
396 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
397 | makeLastStandard = false; |
398 | setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]); |
399 | buffer = QString(); |
400 | break; |
401 | case StateCCommentEnd2: |
402 | if (makeLastStandard) |
403 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
404 | makeLastStandard = false; |
405 | setFormat(start: i, count: 1, format: m_formats[ScriptCommentFormat]); |
406 | // parentheses.push_back(Parenthesis(Parenthesis::Closed, QLatin1Char('/'), i)); |
407 | buffer = QString(); |
408 | break; |
409 | case StateStringStart: |
410 | if (makeLastStandard) |
411 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
412 | makeLastStandard = false; |
413 | setFormat(start: i, count: 1, format: emptyFormat); |
414 | buffer = QString(); |
415 | break; |
416 | case StateString: |
417 | if (makeLastStandard) |
418 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
419 | makeLastStandard = false; |
420 | setFormat(start: i, count: 1, format: m_formats[ScriptStringFormat]); |
421 | buffer = QString(); |
422 | break; |
423 | case StateStringEnd: |
424 | if (makeLastStandard) |
425 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
426 | makeLastStandard = false; |
427 | setFormat(start: i, count: 1, format: emptyFormat); |
428 | buffer = QString(); |
429 | break; |
430 | case StateString2Start: |
431 | if (makeLastStandard) |
432 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
433 | makeLastStandard = false; |
434 | setFormat(start: i, count: 1, format: emptyFormat); |
435 | buffer = QString(); |
436 | break; |
437 | case StateString2: |
438 | if (makeLastStandard) |
439 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
440 | makeLastStandard = false; |
441 | setFormat(start: i, count: 1, format: m_formats[ScriptStringFormat]); |
442 | buffer = QString(); |
443 | break; |
444 | case StateString2End: |
445 | if (makeLastStandard) |
446 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
447 | makeLastStandard = false; |
448 | setFormat(start: i, count: 1, format: emptyFormat); |
449 | buffer = QString(); |
450 | break; |
451 | case StateNumber: |
452 | if (makeLastStandard) |
453 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
454 | makeLastStandard = false; |
455 | setFormat(start: i, count: 1, format: m_formats[ScriptNumberFormat]); |
456 | buffer = QString(); |
457 | break; |
458 | case StatePreProcessor: |
459 | if (makeLastStandard) |
460 | setFormat(start: i - 1, count: 1, format: emptyFormat); |
461 | makeLastStandard = false; |
462 | setFormat(start: i, count: 1, format: m_formats[ScriptPreprocessorFormat]); |
463 | buffer = QString(); |
464 | break; |
465 | } |
466 | |
467 | lastChar = c; |
468 | i++; |
469 | if (i >= text.length()) { |
470 | #if 0 |
471 | if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) { |
472 | userData->setHasClosingCollapse(false); |
473 | userData->setCollapseMode(TextBlockUserData::NoCollapse); |
474 | } |
475 | int collapse = Parenthesis::collapseAtPos(parentheses); |
476 | if (collapse >= 0) { |
477 | if (collapse == firstNonSpace) |
478 | TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(TextBlockUserData::CollapseThis); |
479 | else |
480 | TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(TextBlockUserData::CollapseAfter); |
481 | } |
482 | if (Parenthesis::hasClosingCollapse(parentheses)) { |
483 | TextEditDocumentLayout::userData(currentBlock())->setHasClosingCollapse(true); |
484 | } |
485 | #endif |
486 | |
487 | break; |
488 | } |
489 | } |
490 | |
491 | highlightWord(currentPos: text.length(), buffer); |
492 | |
493 | switch (state) { |
494 | case StateCComment: |
495 | case StateCCommentEnd1: |
496 | case StateCCommentStart2: |
497 | state = StateCComment; |
498 | break; |
499 | case StateString: |
500 | // quotes cannot span multiple lines, so if somebody starts |
501 | // typing a quoted string we don't need to look for the ending |
502 | // quote in another line (or highlight until the end of the |
503 | // document) and therefore slow down editing. |
504 | state = StateStandard; |
505 | break; |
506 | case StateString2: |
507 | state = StateStandard; |
508 | break; |
509 | default: |
510 | state = StateStandard; |
511 | break; |
512 | } |
513 | |
514 | #if 0 |
515 | TextEditDocumentLayout::setParentheses(currentBlock(), parentheses); |
516 | #endif |
517 | |
518 | setCurrentBlockState((braceDepth << 8) | state); |
519 | } |
520 | |
521 | void QScriptSyntaxHighlighter::highlightWord(int currentPos, const QString &buffer) |
522 | { |
523 | if (buffer.isEmpty()) |
524 | return; |
525 | |
526 | // try to highlight Qt 'identifiers' like QObject and Q_PROPERTY |
527 | // but don't highlight words like 'Query' |
528 | if (buffer.length() > 1 |
529 | && buffer.at(i: 0) == QLatin1Char('Q') |
530 | && (buffer.at(i: 1).isUpper() |
531 | || buffer.at(i: 1) == QLatin1Char('_') |
532 | || buffer.at(i: 1) == QLatin1Char('t'))) { |
533 | setFormat(start: currentPos - buffer.length(), count: buffer.length(), format: m_formats[ScriptTypeFormat]); |
534 | } else { |
535 | if (isKeyword(word: buffer)) |
536 | setFormat(start: currentPos - buffer.length(), count: buffer.length(), format: m_formats[ScriptKeywordFormat]); |
537 | } |
538 | } |
539 | |
540 | QT_END_NAMESPACE |
541 | |
542 | #endif // QT_NO_SYNTAXHIGHLIGHTER |
543 | |
544 | |