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