/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtSCriptTools module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL21$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** As a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qscriptsyntaxhighlighter_p.h" #include #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; }; static bool operator<(const KeywordHelper &helper, const char *kw) { return helper.needle < QLatin1String(kw); } static 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 KeywordHelper keywordHelper(word); const char * const *kw = std::lower_bound(start, end, keywordHelper); return kw != end && !(keywordHelper < *kw); } 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; for (;;) { const QChar c = text.at(i); if (lastWasBackSlash) { input = InputSep; } else { switch (c.toLatin1()) { 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; } 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(); } } break; case StateCommentStart1: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = true; buffer = QString(); break; case StateCCommentStart2: setFormat(i - 1, 2, m_formats[ScriptCommentFormat]); makeLastStandard = false; // parentheses.push_back(Parenthesis(Parenthesis::Opened, QLatin1Char('/'), i-1)); buffer = QString(); break; case StateScriptCommentStart2: setFormat(i - 1, 2, m_formats[ScriptCommentFormat]); makeLastStandard = false; buffer = QString(); break; case StateCComment: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptCommentFormat]); buffer = QString(); break; case StateScriptComment: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptCommentFormat]); buffer = QString(); break; case StateCCommentEnd1: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptCommentFormat]); buffer = QString(); 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(); break; case StateStringStart: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, emptyFormat); buffer = QString(); break; case StateString: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptStringFormat]); buffer = QString(); break; case StateStringEnd: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, emptyFormat); buffer = QString(); break; case StateString2Start: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, emptyFormat); buffer = QString(); break; case StateString2: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptStringFormat]); buffer = QString(); break; case StateString2End: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, emptyFormat); buffer = QString(); break; case StateNumber: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptNumberFormat]); buffer = QString(); break; case StatePreProcessor: if (makeLastStandard) setFormat(i - 1, 1, emptyFormat); makeLastStandard = false; setFormat(i, 1, m_formats[ScriptPreprocessorFormat]); buffer = QString(); 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