1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "glslhighlighter.h"
27 #include "glsleditor.h"
28 #include <glsl/glsllexer.h>
29 #include <glsl/glslparser.h>
30 
31 #include <texteditor/textdocumentlayout.h>
32 #include <texteditor/textdocument.h>
33 
34 #include <utils/porting.h>
35 
36 #include <QDebug>
37 
38 using namespace TextEditor;
39 
40 const TextStyle GLSLReservedKeyword = C_REMOVED_LINE;
41 
42 namespace GlslEditor {
43 namespace Internal {
44 
GlslHighlighter()45 GlslHighlighter::GlslHighlighter()
46 {
47     setDefaultTextFormatCategories();
48 }
49 
highlightBlock(const QString & text)50 void GlslHighlighter::highlightBlock(const QString &text)
51 {
52     const int previousState = previousBlockState();
53     int state = 0, initialBraceDepth = 0;
54     if (previousState != -1) {
55         state = previousState & 0xff;
56         initialBraceDepth = previousState >> 8;
57     }
58 
59     int braceDepth = initialBraceDepth;
60 
61     const QByteArray data = text.toLatin1();
62     GLSL::Lexer lex(/*engine=*/ nullptr, data.constData(), data.size());
63     lex.setState(state);
64     lex.setScanKeywords(false);
65     lex.setScanComments(true);
66     const int variant = languageVariant(parent()
67                          ? static_cast<TextDocument*>(parent())->mimeType()
68                          : QString());
69     lex.setVariant(variant);
70 
71     int initialState = state;
72 
73     QList<GLSL::Token> tokens;
74     GLSL::Token tk;
75     do {
76         lex.yylex(&tk);
77         tokens.append(tk);
78     } while (tk.isNot(GLSL::Parser::EOF_SYMBOL));
79 
80     state = lex.state(); // refresh the state
81 
82     int foldingIndent = initialBraceDepth;
83     if (TextBlockUserData *userData = TextDocumentLayout::textUserData(currentBlock())) {
84         userData->setFoldingIndent(0);
85         userData->setFoldingStartIncluded(false);
86         userData->setFoldingEndIncluded(false);
87     }
88 
89     if (tokens.isEmpty()) {
90         setCurrentBlockState(previousState);
91         TextDocumentLayout::clearParentheses(currentBlock());
92         if (!text.isEmpty()) // the empty line can still contain whitespace
93             setFormat(0, text.length(), formatForCategory(C_VISUAL_WHITESPACE));
94         TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
95         return;
96     }
97 
98     const int firstNonSpace = tokens.first().begin();
99 
100     Parentheses parentheses;
101     parentheses.reserve(20); // assume wizard level ;-)
102 
103     bool highlightAsPreprocessor = false;
104 
105     for (int i = 0; i < tokens.size(); ++i) {
106         const GLSL::Token &tk = tokens.at(i);
107 
108         int previousTokenEnd = 0;
109         if (i != 0) {
110             // mark the whitespaces
111             previousTokenEnd = tokens.at(i - 1).begin() +
112                                tokens.at(i - 1).length;
113         }
114 
115         if (previousTokenEnd != tk.begin()) {
116             setFormat(previousTokenEnd, tk.begin() - previousTokenEnd,
117                       formatForCategory(C_VISUAL_WHITESPACE));
118         }
119 
120         if (tk.is(GLSL::Parser::T_LEFT_PAREN) || tk.is(GLSL::Parser::T_LEFT_BRACE) || tk.is(GLSL::Parser::T_LEFT_BRACKET)) {
121             const QChar c = text.at(tk.begin());
122             parentheses.append(Parenthesis(Parenthesis::Opened, c, tk.begin()));
123             if (tk.is(GLSL::Parser::T_LEFT_BRACE)) {
124                 ++braceDepth;
125 
126                 // if a folding block opens at the beginning of a line, treat the entire line
127                 // as if it were inside the folding block
128                 if (tk.begin() == firstNonSpace) {
129                     ++foldingIndent;
130                     TextDocumentLayout::userData(currentBlock())->setFoldingStartIncluded(true);
131                 }
132             }
133         } else if (tk.is(GLSL::Parser::T_RIGHT_PAREN) || tk.is(GLSL::Parser::T_RIGHT_BRACE) || tk.is(GLSL::Parser::T_RIGHT_BRACKET)) {
134             const QChar c = text.at(tk.begin());
135             parentheses.append(Parenthesis(Parenthesis::Closed, c, tk.begin()));
136             if (tk.is(GLSL::Parser::T_RIGHT_BRACE)) {
137                 --braceDepth;
138                 if (braceDepth < foldingIndent) {
139                     // unless we are at the end of the block, we reduce the folding indent
140                     if (i == tokens.size()-1 || tokens.at(i+1).is(GLSL::Parser::T_SEMICOLON))
141                         TextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
142                     else
143                         foldingIndent = qMin(braceDepth, foldingIndent);
144                 }
145             }
146         }
147 
148         bool highlightCurrentWordAsPreprocessor = highlightAsPreprocessor;
149 
150         if (highlightAsPreprocessor)
151             highlightAsPreprocessor = false;
152 
153         if (false /* && i == 0 && tk.is(GLSL::Parser::T_POUND)*/) {
154             highlightLine(text, tk.begin(), tk.length, formatForCategory(C_PREPROCESSOR));
155             highlightAsPreprocessor = true;
156 
157         } else if (highlightCurrentWordAsPreprocessor
158                    && isPPKeyword(Utils::midView(text, tk.begin(), tk.length))) {
159             setFormat(tk.begin(), tk.length, formatForCategory(C_PREPROCESSOR));
160 
161         } else if (tk.is(GLSL::Parser::T_NUMBER)) {
162             setFormat(tk.begin(), tk.length, formatForCategory(C_NUMBER));
163 
164         } else if (tk.is(GLSL::Parser::T_COMMENT)) {
165             highlightLine(text, tk.begin(), tk.length, formatForCategory(C_COMMENT));
166 
167             // we need to insert a close comment parenthesis, if
168             //  - the line starts in a C Comment (initalState != 0)
169             //  - the first token of the line is a T_COMMENT (i == 0 && tk.is(T_COMMENT))
170             //  - is not a continuation line (tokens.size() > 1 || ! state)
171             if (initialState && i == 0 && (tokens.size() > 1 || ! state)) {
172                 --braceDepth;
173                 // unless we are at the end of the block, we reduce the folding indent
174                 if (i == tokens.size()-1)
175                     TextDocumentLayout::userData(currentBlock())->setFoldingEndIncluded(true);
176                 else
177                     foldingIndent = qMin(braceDepth, foldingIndent);
178                 const int tokenEnd = tk.begin() + tk.length - 1;
179                 parentheses.append(Parenthesis(Parenthesis::Closed, QLatin1Char('-'), tokenEnd));
180 
181                 // clear the initial state.
182                 initialState = 0;
183             }
184 
185         } else if (tk.is(GLSL::Parser::T_IDENTIFIER)) {
186             int kind = lex.findKeyword(data.constData() + tk.position, tk.length);
187             if (kind == GLSL::Parser::T_GLRESERVED)
188                 setFormat(tk.position, tk.length, formatForCategory(GLSLReservedKeyword));
189             else if (kind != GLSL::Parser::T_IDENTIFIER)
190                 setFormat(tk.position, tk.length, formatForCategory(C_KEYWORD));
191         }
192     }
193 
194     // mark the trailing white spaces
195     {
196         const GLSL::Token tk = tokens.last();
197         const int lastTokenEnd = tk.begin() + tk.length;
198         if (text.length() > lastTokenEnd)
199             highlightLine(text, lastTokenEnd, text.length() - lastTokenEnd, QTextCharFormat());
200     }
201 
202     if (! initialState && state && ! tokens.isEmpty()) {
203         parentheses.append(Parenthesis(Parenthesis::Opened, QLatin1Char('+'),
204                                        tokens.last().begin()));
205         ++braceDepth;
206     }
207 
208     TextDocumentLayout::setParentheses(currentBlock(), parentheses);
209 
210     // if the block is ifdefed out, we only store the parentheses, but
211 
212     // do not adjust the brace depth.
213     if (TextDocumentLayout::ifdefedOut(currentBlock())) {
214         braceDepth = initialBraceDepth;
215         foldingIndent = initialBraceDepth;
216     }
217 
218     TextDocumentLayout::setFoldingIndent(currentBlock(), foldingIndent);
219 
220     // optimization: if only the brace depth changes, we adjust subsequent blocks
221     // to have QSyntaxHighlighter stop the rehighlighting
222     int currentState = currentBlockState();
223     if (currentState != -1) {
224         int oldState = currentState & 0xff;
225         int oldBraceDepth = currentState >> 8;
226         if (oldState == lex.state() && oldBraceDepth != braceDepth) {
227             int delta = braceDepth - oldBraceDepth;
228             QTextBlock block = currentBlock().next();
229             while (block.isValid() && block.userState() != -1) {
230                 TextDocumentLayout::changeBraceDepth(block, delta);
231                 TextDocumentLayout::changeFoldingIndent(block, delta);
232                 block = block.next();
233             }
234         }
235     }
236 
237     setCurrentBlockState((braceDepth << 8) | lex.state());
238 }
239 
highlightLine(const QString & text,int position,int length,const QTextCharFormat & format)240 void GlslHighlighter::highlightLine(const QString &text, int position, int length,
241                                 const QTextCharFormat &format)
242 {
243     const QTextCharFormat visualSpaceFormat = formatForCategory(C_VISUAL_WHITESPACE);
244 
245     const int end = position + length;
246     int index = position;
247 
248     while (index != end) {
249         const bool isSpace = text.at(index).isSpace();
250         const int start = index;
251 
252         do { ++index; }
253         while (index != end && text.at(index).isSpace() == isSpace);
254 
255         const int tokenLength = index - start;
256         if (isSpace)
257             setFormat(start, tokenLength, visualSpaceFormat);
258         else if (format.isValid())
259             setFormat(start, tokenLength, format);
260     }
261 }
262 
isPPKeyword(QStringView text) const263 bool GlslHighlighter::isPPKeyword(QStringView text) const
264 {
265     switch (text.length())
266     {
267     case 2:
268         if (text.at(0) == QLatin1Char('i') && text.at(1) == QLatin1Char('f'))
269             return true;
270         break;
271 
272     case 4:
273         if (text.at(0) == QLatin1Char('e') && text == QLatin1String("elif"))
274             return true;
275         else if (text.at(0) == QLatin1Char('e') && text == QLatin1String("else"))
276             return true;
277         break;
278 
279     case 5:
280         if (text.at(0) == QLatin1Char('i') && text == QLatin1String("ifdef"))
281             return true;
282         else if (text.at(0) == QLatin1Char('u') && text == QLatin1String("undef"))
283             return true;
284         else if (text.at(0) == QLatin1Char('e') && text == QLatin1String("endif"))
285             return true;
286         else if (text.at(0) == QLatin1Char('e') && text == QLatin1String("error"))
287             return true;
288         break;
289 
290     case 6:
291         if (text.at(0) == QLatin1Char('i') && text == QLatin1String("ifndef"))
292             return true;
293         if (text.at(0) == QLatin1Char('i') && text == QLatin1String("import"))
294             return true;
295         else if (text.at(0) == QLatin1Char('d') && text == QLatin1String("define"))
296             return true;
297         else if (text.at(0) == QLatin1Char('p') && text == QLatin1String("pragma"))
298             return true;
299         break;
300 
301     case 7:
302         if (text.at(0) == QLatin1Char('i') && text == QLatin1String("include"))
303             return true;
304         else if (text.at(0) == QLatin1Char('w') && text == QLatin1String("warning"))
305             return true;
306         break;
307 
308     case 12:
309         if (text.at(0) == QLatin1Char('i') && text == QLatin1String("include_next"))
310             return true;
311         break;
312 
313     default:
314         break;
315     }
316 
317     return false;
318 }
319 
320 } // namespace Internal
321 } // namespace GlslEditor
322