/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the QtSCriptTools module of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "qscriptsyntaxhighlighter_p.h" | |
#include "private/qfunctions_p.h" | |
#ifndef QT_NO_SYNTAXHIGHLIGHTER | |
QT_BEGIN_NAMESPACE | |
enum ScriptIds { | |
Comment = 1, | |
Number, | |
String, | |
Type, | |
Keyword, | |
PreProcessor, | |
Label | |
}; | |
#define MAX_KEYWORD 63 | |
static const char *const keywords[MAX_KEYWORD] = { | |
"Infinity", | |
"NaN", | |
"abstract", | |
"boolean", | |
"break", | |
"byte", | |
"case", | |
"catch", | |
"char", | |
"class", | |
"const", | |
"constructor", | |
"continue", | |
"debugger", | |
"default", | |
"delete", | |
"do", | |
"double", | |
"else", | |
"enum", | |
"export", | |
"extends", | |
"false", | |
"final", | |
"finally", | |
"float", | |
"for", | |
"function", | |
"goto", | |
"if", | |
"implements", | |
"import", | |
"in", | |
"instanceof", | |
"int", | |
"interface", | |
"long", | |
"native", | |
"new", | |
"package", | |
"private", | |
"protected", | |
"public", | |
"return", | |
"short", | |
"static", | |
"super", | |
"switch", | |
"synchronized", | |
"this", | |
"throw", | |
"throws", | |
"transient", | |
"true", | |
"try", | |
"typeof", | |
"undefined", | |
"var", | |
"void", | |
"volatile", | |
"while", | |
"with", // end of array | |
0 | |
}; | |
struct KeywordHelper | |
{ | |
inline KeywordHelper(const QString &word) : needle(word) {} | |
const QString needle; | |
}; | |
Q_STATIC_GLOBAL_OPERATOR bool operator<(const KeywordHelper &helper, const char *kw) | |
{ | |
return helper.needle < QLatin1String(kw); | |
} | |
Q_STATIC_GLOBAL_OPERATOR bool operator<(const char *kw, const KeywordHelper &helper) | |
{ | |
return QLatin1String(kw) < helper.needle; | |
} | |
static bool isKeyword(const QString &word) | |
{ | |
const char * const *start = &keywords[0]; | |
const char * const *end = &keywords[MAX_KEYWORD - 1]; | |
const char * const *kw = qBinaryFind(start, end, KeywordHelper(word)); | |
return kw != end; | |
} | |
QScriptSyntaxHighlighter::QScriptSyntaxHighlighter(QTextDocument *document) | |
: QSyntaxHighlighter(document) | |
{ | |
m_formats[ScriptNumberFormat].setForeground(Qt::darkBlue); | |
m_formats[ScriptStringFormat].setForeground(Qt::darkGreen); | |
m_formats[ScriptTypeFormat].setForeground(Qt::darkMagenta); | |
m_formats[ScriptKeywordFormat].setForeground(Qt::darkYellow); | |
m_formats[ScriptPreprocessorFormat].setForeground(Qt::darkBlue); | |
m_formats[ScriptLabelFormat].setForeground(Qt::darkRed); | |
m_formats[ScriptCommentFormat].setForeground(Qt::darkGreen); | |
m_formats[ScriptCommentFormat].setFontItalic(true); | |
} | |
QScriptSyntaxHighlighter::~QScriptSyntaxHighlighter() | |
{ | |
} | |
void QScriptSyntaxHighlighter::highlightBlock(const QString &text) | |
{ | |
// states | |
enum States { StateStandard, StateCommentStart1, StateCCommentStart2, | |
StateScriptCommentStart2, StateCComment, StateScriptComment, StateCCommentEnd1, | |
StateCCommentEnd2, StateStringStart, StateString, StateStringEnd, | |
StateString2Start, StateString2, StateString2End, | |
StateNumber, StatePreProcessor, NumStates }; | |
// tokens | |
enum Tokens { InputAlpha, InputNumber, InputAsterix, InputSlash, InputParen, | |
InputSpace, InputHash, InputQuotation, InputApostrophe, InputSep, NumTokens }; | |
static uchar table[NumStates][NumTokens] = { | |
{ StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStandard | |
{ StateStandard, StateNumber, StateCCommentStart2, StateScriptCommentStart2, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCommentStart1 | |
{ StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentStart2 | |
{ StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // ScriptCommentStart2 | |
{ StateCComment, StateCComment, StateCCommentEnd1, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCComment | |
{ StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment, StateScriptComment }, // StateScriptComment | |
{ StateCComment, StateCComment, StateCCommentEnd1, StateCCommentEnd2, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment, StateCComment }, // StateCCommentEnd1 | |
{ StateStandard, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateCCommentEnd2 | |
{ StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateStringStart | |
{ StateString, StateString, StateString, StateString, StateString, StateString, StateString, StateStringEnd, StateString, StateString }, // StateString | |
{ StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateStringEnd | |
{ StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2Start | |
{ StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2, StateString2End, StateString2 }, // StateString2 | |
{ StateStandard, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateString2End | |
{ StateNumber, StateNumber, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard }, // StateNumber | |
{ StatePreProcessor, StateStandard, StateStandard, StateCommentStart1, StateStandard, StateStandard, StatePreProcessor, StateStringStart, StateString2Start, StateStandard } // StatePreProcessor | |
}; | |
QString buffer; | |
buffer.reserve(text.length()); | |
QTextCharFormat emptyFormat; | |
int state = StateStandard; | |
int braceDepth = 0; | |
const int previousState = previousBlockState(); | |
if (previousState != -1) { | |
state = previousState & 0xff; | |
braceDepth = previousState >> 8; | |
} | |
if (text.isEmpty()) { | |
setCurrentBlockState(previousState); | |
#if 0 | |
TextEditDocumentLayout::clearParentheses(currentBlock()); | |
#endif | |
return; | |
} | |
#if 0 | |
Parentheses parentheses; | |
parentheses.reserve(20); // assume wizard level ;-) | |
#endif | |
int input = -1; | |
int i = 0; | |
bool lastWasBackSlash = false; | |
bool makeLastStandard = false; | |
static const QString alphabeth = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); | |
static const QString mathChars = QLatin1String("xXeE"); | |
static const QString numbers = QLatin1String("0123456789"); | |
bool questionMark = false; | |
QChar lastChar; | |
int firstNonSpace = -1; | |
int lastNonSpace = -1; | |
for (;;) { | |
const QChar c = text.at(i); | |
if (lastWasBackSlash) { | |
input = InputSep; | |
} else { | |
switch (c.toAscii()) { | |
case '*': | |
input = InputAsterix; | |
break; | |
case '/': | |
input = InputSlash; | |
break; | |
case '{': | |
braceDepth++; | |
// fall through | |
case '(': case '[': | |
input = InputParen; | |
switch (state) { | |
case StateStandard: | |
case StateNumber: | |
case StatePreProcessor: | |
case StateCCommentEnd2: | |
case StateCCommentEnd1: | |
case StateString2End: | |
case StateStringEnd: | |
// parentheses.push_back(Parenthesis(Parenthesis::Opened, c, i)); | |
break; | |
default: | |
break; | |
} | |
break; | |
case '}': | |
if (--braceDepth < 0) | |
braceDepth = 0; | |
// fall through | |
case ')': case ']': | |
input = InputParen; | |
switch (state) { | |
case StateStandard: | |
case StateNumber: | |
case StatePreProcessor: | |
case StateCCommentEnd2: | |
case StateCCommentEnd1: | |
case StateString2End: | |
case StateStringEnd: | |
// parentheses.push_back(Parenthesis(Parenthesis::Closed, c, i)); | |
break; | |
default: | |
break; | |
} | |
break; | |
case '#': | |
input = InputHash; | |
break; | |
case '"': | |
input = InputQuotation; | |
break; | |
case '\'': | |
input = InputApostrophe; | |
break; | |
case ' ': | |
input = InputSpace; | |
break; | |
case '1': case '2': case '3': case '4': case '5': | |
case '6': case '7': case '8': case '9': case '0': | |
if (alphabeth.contains(lastChar) | |
&& (!mathChars.contains(lastChar) || !numbers.contains(text.at(i - 1))) | |
) { | |
input = InputAlpha; | |
} else { | |
if (input == InputAlpha && numbers.contains(lastChar)) | |
input = InputAlpha; | |
else | |
input = InputNumber; | |
} | |
break; | |
case ':': { | |
input = InputAlpha; | |
const QChar colon = QLatin1Char(':'); | |
if (state == StateStandard && !questionMark && lastChar != colon) { | |
const QChar nextChar = i < text.length() - 1 ? text.at(i + 1) : QLatin1Char(' '); | |
if (nextChar != colon) | |
for (int j = 0; j < i; ++j) { | |
if (format(j) == emptyFormat ) | |
setFormat(j, 1, m_formats[ScriptLabelFormat]); | |
} | |
} | |
} break; | |
default: | |
if (!questionMark && c == QLatin1Char('?')) | |
questionMark = true; | |
if (c.isLetter() || c == QLatin1Char('_')) | |
input = InputAlpha; | |
else | |
input = InputSep; | |
break; | |
} | |
} | |
if (input != InputSpace) { | |
if (firstNonSpace < 0) | |
firstNonSpace = i; | |
lastNonSpace = i; | |
} | |
lastWasBackSlash = !lastWasBackSlash && c == QLatin1Char('\\'); | |
if (input == InputAlpha) | |
buffer += c; | |
state = table[state][input]; | |
switch (state) { | |
case StateStandard: { | |
setFormat(i, 1, emptyFormat); | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
if (input != InputAlpha) { | |
highlightWord(i, buffer); | |
buffer = QString::null; | |
} | |
} break; | |
case StateCommentStart1: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = true; | |
buffer = QString::null; | |
break; | |
case StateCCommentStart2: | |
setFormat(i - 1, 2, m_formats[ScriptCommentFormat]); | |
makeLastStandard = false; | |
// parentheses.push_back(Parenthesis(Parenthesis::Opened, QLatin1Char('/'), i-1)); | |
buffer = QString::null; | |
break; | |
case StateScriptCommentStart2: | |
setFormat(i - 1, 2, m_formats[ScriptCommentFormat]); | |
makeLastStandard = false; | |
buffer = QString::null; | |
break; | |
case StateCComment: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptCommentFormat]); | |
buffer = QString::null; | |
break; | |
case StateScriptComment: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptCommentFormat]); | |
buffer = QString::null; | |
break; | |
case StateCCommentEnd1: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptCommentFormat]); | |
buffer = QString::null; | |
break; | |
case StateCCommentEnd2: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptCommentFormat]); | |
// parentheses.push_back(Parenthesis(Parenthesis::Closed, QLatin1Char('/'), i)); | |
buffer = QString::null; | |
break; | |
case StateStringStart: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, emptyFormat); | |
buffer = QString::null; | |
break; | |
case StateString: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptStringFormat]); | |
buffer = QString::null; | |
break; | |
case StateStringEnd: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, emptyFormat); | |
buffer = QString::null; | |
break; | |
case StateString2Start: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, emptyFormat); | |
buffer = QString::null; | |
break; | |
case StateString2: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptStringFormat]); | |
buffer = QString::null; | |
break; | |
case StateString2End: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, emptyFormat); | |
buffer = QString::null; | |
break; | |
case StateNumber: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptNumberFormat]); | |
buffer = QString::null; | |
break; | |
case StatePreProcessor: | |
if (makeLastStandard) | |
setFormat(i - 1, 1, emptyFormat); | |
makeLastStandard = false; | |
setFormat(i, 1, m_formats[ScriptPreprocessorFormat]); | |
buffer = QString::null; | |
break; | |
} | |
lastChar = c; | |
i++; | |
if (i >= text.length()) { | |
#if 0 | |
if (TextBlockUserData *userData = TextEditDocumentLayout::testUserData(currentBlock())) { | |
userData->setHasClosingCollapse(false); | |
userData->setCollapseMode(TextBlockUserData::NoCollapse); | |
} | |
int collapse = Parenthesis::collapseAtPos(parentheses); | |
if (collapse >= 0) { | |
if (collapse == firstNonSpace) | |
TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(TextBlockUserData::CollapseThis); | |
else | |
TextEditDocumentLayout::userData(currentBlock())->setCollapseMode(TextBlockUserData::CollapseAfter); | |
} | |
if (Parenthesis::hasClosingCollapse(parentheses)) { | |
TextEditDocumentLayout::userData(currentBlock())->setHasClosingCollapse(true); | |
} | |
#endif | |
break; | |
} | |
} | |
highlightWord(text.length(), buffer); | |
switch (state) { | |
case StateCComment: | |
case StateCCommentEnd1: | |
case StateCCommentStart2: | |
state = StateCComment; | |
break; | |
case StateString: | |
// quotes cannot span multiple lines, so if somebody starts | |
// typing a quoted string we don't need to look for the ending | |
// quote in another line (or highlight until the end of the | |
// document) and therefore slow down editing. | |
state = StateStandard; | |
break; | |
case StateString2: | |
state = StateStandard; | |
break; | |
default: | |
state = StateStandard; | |
break; | |
} | |
#if 0 | |
TextEditDocumentLayout::setParentheses(currentBlock(), parentheses); | |
#endif | |
setCurrentBlockState((braceDepth << 8) | state); | |
} | |
void QScriptSyntaxHighlighter::highlightWord(int currentPos, const QString &buffer) | |
{ | |
if (buffer.isEmpty()) | |
return; | |
// try to highlight Qt 'identifiers' like QObject and Q_PROPERTY | |
// but don't highlight words like 'Query' | |
if (buffer.length() > 1 | |
&& buffer.at(0) == QLatin1Char('Q') | |
&& (buffer.at(1).isUpper() | |
|| buffer.at(1) == QLatin1Char('_') | |
|| buffer.at(1) == QLatin1Char('t'))) { | |
setFormat(currentPos - buffer.length(), buffer.length(), m_formats[ScriptTypeFormat]); | |
} else { | |
if (isKeyword(buffer)) | |
setFormat(currentPos - buffer.length(), buffer.length(), m_formats[ScriptKeywordFormat]); | |
} | |
} | |
QT_END_NAMESPACE | |
#endif // QT_NO_SYNTAXHIGHLIGHTER | |