OSDN Git Service

It's 2011 now.
[qt-creator-jp/qt-creator-jp.git] / src / plugins / glsleditor / glslhighlighter.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33 #include "glslhighlighter.h"
34 #include "glsleditor.h"
35 #include <glsl/glsllexer.h>
36 #include <glsl/glslparser.h>
37 #include <texteditor/basetextdocumentlayout.h>
38
39 #include <QtCore/QDebug>
40
41 using namespace GLSLEditor;
42 using namespace GLSLEditor::Internal;
43 using namespace TextEditor;
44
45 Highlighter::Highlighter(GLSLTextEditor *editor, QTextDocument *parent)
46     : TextEditor::SyntaxHighlighter(parent), m_editor(editor)
47 {
48 }
49
50 Highlighter::~Highlighter()
51 {
52
53 }
54
55 void Highlighter::setFormats(const QVector<QTextCharFormat> &formats)
56 {
57     qCopy(formats.begin(), formats.end(), m_formats);
58 }
59
60 void Highlighter::highlightBlock(const QString &text)
61 {
62     const int previousState = previousBlockState();
63     int state = 0, initialBraceDepth = 0;
64     if (previousState != -1) {
65         state = previousState & 0xff;
66         initialBraceDepth = previousState >> 8;
67     }
68
69     int braceDepth = initialBraceDepth;
70
71     const QByteArray data = text.toLatin1();
72     GLSL::Lexer lex(/*engine=*/ 0, data.constData(), data.size());
73     lex.setState(state);
74     lex.setScanKeywords(false);
75     lex.setScanComments(true);
76     const int variant = m_editor->languageVariant();
77     lex.setVariant(variant);
78
79     int initialState = state;
80
81     QList<GLSL::Token> tokens;
82     GLSL::Token tk;
83     do {
84         lex.yylex(&tk);
85         tokens.append(tk);
86     } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
87
88     state = lex.state(); // refresh the state
89
90     int foldingIndent = initialBraceDepth;
91     if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(currentBlock())) {
92         userData->setFoldingIndent(0);
93         userData->setFoldingStartIncluded(false);
94         userData->setFoldingEndIncluded(false);
95     }
96
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);
103         return;
104     }
105
106     const int firstNonSpace = tokens.first().begin();
107
108     Parentheses parentheses;
109     parentheses.reserve(20); // assume wizard level ;-)
110
111     bool highlightAsPreprocessor = false;
112
113     for (int i = 0; i < tokens.size(); ++i) {
114         const GLSL::Token &tk = tokens.at(i);
115
116         int previousTokenEnd = 0;
117         if (i != 0) {
118             // mark the whitespaces
119             previousTokenEnd = tokens.at(i - 1).begin() +
120                                tokens.at(i - 1).length;
121         }
122
123         if (previousTokenEnd != tk.begin()) {
124             setFormat(previousTokenEnd, tk.begin() - previousTokenEnd,
125                       m_formats[GLSLVisualWhitespace]);
126         }
127
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)) {
132                 ++braceDepth;
133
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) {
137                     ++foldingIndent;
138                     BaseTextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
139                 }
140             }
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)) {
145                 --braceDepth;
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);
150                     else
151                         foldingIndent = qMin(braceDepth, foldingIndent);
152                 }
153             }
154         }
155
156         bool highlightCurrentWordAsPreprocessor = highlightAsPreprocessor;
157
158         if (highlightAsPreprocessor)
159             highlightAsPreprocessor = false;
160
161         if (false /* && i == 0 && tk.is(GLSL::Parser::T_POUND)*/) {
162             highlightLine(text, tk.begin(), tk.length, m_formats[GLSLPreprocessorFormat]);
163             highlightAsPreprocessor = true;
164
165         } else if (highlightCurrentWordAsPreprocessor && isPPKeyword(text.midRef(tk.begin(), tk.length)))
166             setFormat(tk.begin(), tk.length, m_formats[GLSLPreprocessorFormat]);
167
168         else if (tk.is(GLSL::Parser::T_NUMBER))
169             setFormat(tk.begin(), tk.length, m_formats[GLSLNumberFormat]);
170
171         else if (tk.is(GLSL::Parser::T_COMMENT)) {
172             highlightLine(text, tk.begin(), tk.length, m_formats[GLSLCommentFormat]);
173
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)) {
179                 --braceDepth;
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);
183                 else
184                     foldingIndent = qMin(braceDepth, foldingIndent);
185                 const int tokenEnd = tk.begin() + tk.length - 1;
186                 parentheses.append(Parenthesis(Parenthesis::Closed, QLatin1Char('-'), tokenEnd));
187
188                 // clear the initial state.
189                 initialState = 0;
190             }
191
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]);
198         }
199     }
200
201     // mark the trailing white spaces
202     {
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());
207     }
208
209     if (! initialState && state && ! tokens.isEmpty()) {
210         parentheses.append(Parenthesis(Parenthesis::Opened, QLatin1Char('+'),
211                                        tokens.last().begin()));
212         ++braceDepth;
213     }
214
215     BaseTextDocumentLayout::setParentheses(currentBlock(), parentheses);
216
217     // if the block is ifdefed out, we only store the parentheses, but
218
219     // do not adjust the brace depth.
220     if (BaseTextDocumentLayout::ifdefedOut(currentBlock())) {
221         braceDepth = initialBraceDepth;
222         foldingIndent = initialBraceDepth;
223     }
224
225     BaseTextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
226
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();
240             }
241         }
242     }
243
244     setCurrentBlockState((braceDepth << 8) | lex.state());
245 }
246
247 void Highlighter::highlightLine(const QString &text, int position, int length,
248                                 const QTextCharFormat &format)
249 {
250     const QTextCharFormat visualSpaceFormat = m_formats[GLSLVisualWhitespace];
251
252     const int end = position + length;
253     int index = position;
254
255     while (index != end) {
256         const bool isSpace = text.at(index).isSpace();
257         const int start = index;
258
259         do { ++index; }
260         while (index != end && text.at(index).isSpace() == isSpace);
261
262         const int tokenLength = index - start;
263         if (isSpace)
264             setFormat(start, tokenLength, visualSpaceFormat);
265         else if (format.isValid())
266             setFormat(start, tokenLength, format);
267     }
268 }
269
270 bool Highlighter::isPPKeyword(const QStringRef &text) const
271 {
272     switch (text.length())
273     {
274     case 2:
275         if (text.at(0) == 'i' && text.at(1) == 'f')
276             return true;
277         break;
278
279     case 4:
280         if (text.at(0) == 'e' && text == QLatin1String("elif"))
281             return true;
282         else if (text.at(0) == 'e' && text == QLatin1String("else"))
283             return true;
284         break;
285
286     case 5:
287         if (text.at(0) == 'i' && text == QLatin1String("ifdef"))
288             return true;
289         else if (text.at(0) == 'u' && text == QLatin1String("undef"))
290             return true;
291         else if (text.at(0) == 'e' && text == QLatin1String("endif"))
292             return true;
293         else if (text.at(0) == 'e' && text == QLatin1String("error"))
294             return true;
295         break;
296
297     case 6:
298         if (text.at(0) == 'i' && text == QLatin1String("ifndef"))
299             return true;
300         if (text.at(0) == 'i' && text == QLatin1String("import"))
301             return true;
302         else if (text.at(0) == 'd' && text == QLatin1String("define"))
303             return true;
304         else if (text.at(0) == 'p' && text == QLatin1String("pragma"))
305             return true;
306         break;
307
308     case 7:
309         if (text.at(0) == 'i' && text == QLatin1String("include"))
310             return true;
311         else if (text.at(0) == 'w' && text == QLatin1String("warning"))
312             return true;
313         break;
314
315     case 12:
316         if (text.at(0) == 'i' && text == QLatin1String("include_next"))
317             return true;
318         break;
319
320     default:
321         break;
322     }
323
324     return false;
325 }
326
327 #if 0
328 void Highlighter::highlightBlock(const QString &text)
329 {
330     const int previousState = previousBlockState();
331     int state = 0, initialBraceDepth = 0;
332     if (previousState != -1) {
333         state = previousState & 0xff;
334         initialBraceDepth = previousState >> 8;
335     }
336
337     int braceDepth = initialBraceDepth;
338
339     Parentheses parentheses;
340     parentheses.reserve(20); // assume wizard level ;-)
341
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);
349
350     int foldingIndent = initialBraceDepth;
351     if (TextBlockUserData *userData = BaseTextDocumentLayout::testUserData(currentBlock())) {
352         userData->setFoldingIndent(0);
353         userData->setFoldingStartIncluded(false);
354         userData->setFoldingEndIncluded(false);
355     }
356
357     QList<GLSL::Token> tokens;
358     GLSL::Token tk;
359     do {
360         lex.yylex(&tk);
361         tokens.append(tk);
362     } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
363
364     for (int i = 0; i < tokens.size(); ++i) {
365         const GLSL::Token &tk = tokens.at(i);
366
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)) {
381                 ++braceDepth;
382
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) {
386 //                    ++foldingIndent;
387 //                    BaseTextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
388 //                }
389             }
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)) {
394                 --braceDepth;
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);
399                     else
400                         foldingIndent = qMin(braceDepth, foldingIndent);
401                 }
402             }
403         }
404
405     } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
406     setCurrentBlockState((braceDepth << 8) | lex.state());
407 }
408 #endif