1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** 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 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
33 #include "glslhighlighter.h"
34 #include "glsleditor.h"
35 #include <glsl/glsllexer.h>
36 #include <glsl/glslparser.h>
37 #include <texteditor/basetextdocumentlayout.h>
39 #include <QtCore/QDebug>
41 using namespace GLSLEditor;
42 using namespace GLSLEditor::Internal;
43 using namespace TextEditor;
45 Highlighter::Highlighter(GLSLTextEditor *editor, QTextDocument *parent)
46 : TextEditor::SyntaxHighlighter(parent), m_editor(editor)
50 Highlighter::~Highlighter()
55 void Highlighter::setFormats(const QVector<QTextCharFormat> &formats)
57 qCopy(formats.begin(), formats.end(), m_formats);
60 void Highlighter::highlightBlock(const QString &text)
62 const int previousState = previousBlockState();
63 int state = 0, initialBraceDepth = 0;
64 if (previousState != -1) {
65 state = previousState & 0xff;
66 initialBraceDepth = previousState >> 8;
69 int braceDepth = initialBraceDepth;
71 const QByteArray data = text.toLatin1();
72 GLSL::Lexer lex(/*engine=*/ 0, data.constData(), data.size());
74 lex.setScanKeywords(false);
75 lex.setScanComments(true);
76 const int variant = m_editor->languageVariant();
77 lex.setVariant(variant);
79 int initialState = state;
81 QList<GLSL::Token> tokens;
86 } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
88 state = lex.state(); // refresh the state
90 int foldingIndent = initialBraceDepth;
91 if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(currentBlock())) {
92 userData->setFoldingIndent(0);
93 userData->setFoldingStartIncluded(false);
94 userData->setFoldingEndIncluded(false);
97 if (tokens.isEmpty()) {
98 setCurrentBlockState(previousState);
99 BaseTextDocumentLayout::clearParentheses(currentBlock());
100 if (text.length()) // the empty line can still contain whitespace
101 setFormat(0, text.length(), m_formats[GLSLVisualWhitespace]);
102 BaseTextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
106 const int firstNonSpace = tokens.first().begin();
108 Parentheses parentheses;
109 parentheses.reserve(20); // assume wizard level ;-)
111 bool highlightAsPreprocessor = false;
113 for (int i = 0; i < tokens.size(); ++i) {
114 const GLSL::Token &tk = tokens.at(i);
116 int previousTokenEnd = 0;
118 // mark the whitespaces
119 previousTokenEnd = tokens.at(i - 1).begin() +
120 tokens.at(i - 1).length;
123 if (previousTokenEnd != tk.begin()) {
124 setFormat(previousTokenEnd, tk.begin() - previousTokenEnd,
125 m_formats[GLSLVisualWhitespace]);
128 if (tk.is(GLSL::Parser::T_LEFT_PAREN) || tk.is(GLSL::Parser::T_LEFT_BRACE) || tk.is(GLSL::Parser::T_LEFT_BRACKET)) {
129 const QChar c = text.at(tk.begin());
130 parentheses.append(Parenthesis(Parenthesis::Opened, c, tk.begin()));
131 if (tk.is(GLSL::Parser::T_LEFT_BRACE)) {
134 // if a folding block opens at the beginning of a line, treat the entire line
135 // as if it were inside the folding block
136 if (tk.begin() == firstNonSpace) {
138 BaseTextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
141 } else if (tk.is(GLSL::Parser::T_RIGHT_PAREN) || tk.is(GLSL::Parser::T_RIGHT_BRACE) || tk.is(GLSL::Parser::T_RIGHT_BRACKET)) {
142 const QChar c = text.at(tk.begin());
143 parentheses.append(Parenthesis(Parenthesis::Closed, c, tk.begin()));
144 if (tk.is(GLSL::Parser::T_RIGHT_BRACE)) {
146 if (braceDepth < foldingIndent) {
147 // unless we are at the end of the block, we reduce the folding indent
148 if (i == tokens.size()-1 || tokens.at(i+1).is(GLSL::Parser::T_SEMICOLON))
149 BaseTextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
151 foldingIndent = qMin(braceDepth, foldingIndent);
156 bool highlightCurrentWordAsPreprocessor = highlightAsPreprocessor;
158 if (highlightAsPreprocessor)
159 highlightAsPreprocessor = false;
161 if (false /* && i == 0 && tk.is(GLSL::Parser::T_POUND)*/) {
162 highlightLine(text, tk.begin(), tk.length, m_formats[GLSLPreprocessorFormat]);
163 highlightAsPreprocessor = true;
165 } else if (highlightCurrentWordAsPreprocessor && isPPKeyword(text.midRef(tk.begin(), tk.length)))
166 setFormat(tk.begin(), tk.length, m_formats[GLSLPreprocessorFormat]);
168 else if (tk.is(GLSL::Parser::T_NUMBER))
169 setFormat(tk.begin(), tk.length, m_formats[GLSLNumberFormat]);
171 else if (tk.is(GLSL::Parser::T_COMMENT)) {
172 highlightLine(text, tk.begin(), tk.length, m_formats[GLSLCommentFormat]);
174 // we need to insert a close comment parenthesis, if
175 // - the line starts in a C Comment (initalState != 0)
176 // - the first token of the line is a T_COMMENT (i == 0 && tk.is(T_COMMENT))
177 // - is not a continuation line (tokens.size() > 1 || ! state)
178 if (initialState && i == 0 && (tokens.size() > 1 || ! state)) {
180 // unless we are at the end of the block, we reduce the folding indent
181 if (i == tokens.size()-1)
182 BaseTextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
184 foldingIndent = qMin(braceDepth, foldingIndent);
185 const int tokenEnd = tk.begin() + tk.length - 1;
186 parentheses.append(Parenthesis(Parenthesis::Closed, QLatin1Char('-'), tokenEnd));
188 // clear the initial state.
192 } else if (tk.is(GLSL::Parser::T_IDENTIFIER)) {
193 int kind = lex.findKeyword(data.constData() + tk.position, tk.length);
194 if (kind == GLSL::Parser::T_RESERVED)
195 setFormat(tk.position, tk.length, m_formats[GLSLReservedKeyword]);
196 else if (kind != GLSL::Parser::T_IDENTIFIER)
197 setFormat(tk.position, tk.length, m_formats[GLSLKeywordFormat]);
201 // mark the trailing white spaces
203 const GLSL::Token tk = tokens.last();
204 const int lastTokenEnd = tk.begin() + tk.length;
205 if (text.length() > lastTokenEnd)
206 highlightLine(text, lastTokenEnd, text.length() - lastTokenEnd, QTextCharFormat());
209 if (! initialState && state && ! tokens.isEmpty()) {
210 parentheses.append(Parenthesis(Parenthesis::Opened, QLatin1Char('+'),
211 tokens.last().begin()));
215 BaseTextDocumentLayout::setParentheses(currentBlock(), parentheses);
217 // if the block is ifdefed out, we only store the parentheses, but
219 // do not adjust the brace depth.
220 if (BaseTextDocumentLayout::ifdefedOut(currentBlock())) {
221 braceDepth = initialBraceDepth;
222 foldingIndent = initialBraceDepth;
225 BaseTextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
227 // optimization: if only the brace depth changes, we adjust subsequent blocks
228 // to have QSyntaxHighlighter stop the rehighlighting
229 int currentState = currentBlockState();
230 if (currentState != -1) {
231 int oldState = currentState & 0xff;
232 int oldBraceDepth = currentState >> 8;
233 if (oldState == lex.state() && oldBraceDepth != braceDepth) {
234 int delta = braceDepth - oldBraceDepth;
235 QTextBlock block = currentBlock().next();
236 while (block.isValid() && block.userState() != -1) {
237 BaseTextDocumentLayout::changeBraceDepth(block, delta);
238 BaseTextDocumentLayout::changeFoldingIndent(block, delta);
239 block = block.next();
244 setCurrentBlockState((braceDepth << 8) | lex.state());
247 void Highlighter::highlightLine(const QString &text, int position, int length,
248 const QTextCharFormat &format)
250 const QTextCharFormat visualSpaceFormat = m_formats[GLSLVisualWhitespace];
252 const int end = position + length;
253 int index = position;
255 while (index != end) {
256 const bool isSpace = text.at(index).isSpace();
257 const int start = index;
260 while (index != end && text.at(index).isSpace() == isSpace);
262 const int tokenLength = index - start;
264 setFormat(start, tokenLength, visualSpaceFormat);
265 else if (format.isValid())
266 setFormat(start, tokenLength, format);
270 bool Highlighter::isPPKeyword(const QStringRef &text) const
272 switch (text.length())
275 if (text.at(0) == 'i' && text.at(1) == 'f')
280 if (text.at(0) == 'e' && text == QLatin1String("elif"))
282 else if (text.at(0) == 'e' && text == QLatin1String("else"))
287 if (text.at(0) == 'i' && text == QLatin1String("ifdef"))
289 else if (text.at(0) == 'u' && text == QLatin1String("undef"))
291 else if (text.at(0) == 'e' && text == QLatin1String("endif"))
293 else if (text.at(0) == 'e' && text == QLatin1String("error"))
298 if (text.at(0) == 'i' && text == QLatin1String("ifndef"))
300 if (text.at(0) == 'i' && text == QLatin1String("import"))
302 else if (text.at(0) == 'd' && text == QLatin1String("define"))
304 else if (text.at(0) == 'p' && text == QLatin1String("pragma"))
309 if (text.at(0) == 'i' && text == QLatin1String("include"))
311 else if (text.at(0) == 'w' && text == QLatin1String("warning"))
316 if (text.at(0) == 'i' && text == QLatin1String("include_next"))
328 void Highlighter::highlightBlock(const QString &text)
330 const int previousState = previousBlockState();
331 int state = 0, initialBraceDepth = 0;
332 if (previousState != -1) {
333 state = previousState & 0xff;
334 initialBraceDepth = previousState >> 8;
337 int braceDepth = initialBraceDepth;
339 Parentheses parentheses;
340 parentheses.reserve(20); // assume wizard level ;-)
342 const QByteArray data = text.toLatin1();
343 GLSL::Lexer lex(/*engine=*/ 0, data.constData(), data.size());
344 lex.setState(qMax(0, previousState));
345 lex.setScanKeywords(false);
346 lex.setScanComments(true);
347 const int variant = m_editor->languageVariant();
348 lex.setVariant(variant);
350 int foldingIndent = initialBraceDepth;
351 if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(currentBlock())) {
352 userData->setFoldingIndent(0);
353 userData->setFoldingStartIncluded(false);
354 userData->setFoldingEndIncluded(false);
357 QList<GLSL::Token> tokens;
362 } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
364 for (int i = 0; i < tokens.size(); ++i) {
365 const GLSL::Token &tk = tokens.at(i);
367 if (tk.is(GLSL::Parser::T_NUMBER))
368 setFormat(tk.position, tk.length, m_formats[GLSLNumberFormat]);
369 else if (tk.is(GLSL::Parser::T_COMMENT))
370 setFormat(tk.position, tk.length, Qt::darkGreen); // ### FIXME: m_formats[GLSLCommentFormat]);
371 else if (tk.is(GLSL::Parser::T_IDENTIFIER)) {
372 int kind = lex.findKeyword(data.constData() + tk.position, tk.length);
373 if (kind == GLSL::Parser::T_RESERVED)
374 setFormat(tk.position, tk.length, m_formats[GLSLReservedKeyword]);
375 else if (kind != GLSL::Parser::T_IDENTIFIER)
376 setFormat(tk.position, tk.length, m_formats[GLSLKeywordFormat]);
377 } else if (tk.is(GLSL::Parser::T_LEFT_PAREN) || tk.is(GLSL::Parser::T_LEFT_BRACE) || tk.is(GLSL::Parser::T_LEFT_BRACKET)) {
378 const QChar c = text.at(tk.begin());
379 parentheses.append(Parenthesis(Parenthesis::Opened, c, tk.begin()));
380 if (tk.is(GLSL::Parser::T_LEFT_BRACE)) {
383 // if a folding block opens at the beginning of a line, treat the entire line
384 // as if it were inside the folding block
385 // if (tk.begin() == firstNonSpace) {
387 // BaseTextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
390 } else if (tk.is(GLSL::Parser::T_RIGHT_PAREN) || tk.is(GLSL::Parser::T_RIGHT_BRACE) || tk.is(GLSL::Parser::T_RIGHT_BRACKET)) {
391 const QChar c = text.at(tk.begin());
392 parentheses.append(Parenthesis(Parenthesis::Closed, c, tk.begin()));
393 if (tk.is(GLSL::Parser::T_RIGHT_BRACE)) {
395 if (braceDepth < foldingIndent) {
396 // unless we are at the end of the block, we reduce the folding indent
397 if (i == tokens.size()-1 || tokens.at(i+1).is(GLSL::Parser::T_SEMICOLON))
398 BaseTextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
400 foldingIndent = qMin(braceDepth, foldingIndent);
405 } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
406 setCurrentBlockState((braceDepth << 8) | lex.state());