1// Copyright (C) 2021 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "syntaxhighlighter.h"
5
6#include <QtCore/qregularexpression.h>
7
8#include <QtQuick3DRuntimeRender/private/qssgshadermaterialadapter_p.h>
9#include <QtQuick3DRuntimeRender/private/qssgrenderdefaultmaterialshadergenerator_p.h>
10
11#include <QtQuick3DGlslParser/private/glsllexer_p.h>
12#include <QtQuick3DGlslParser/private/glslparser_p.h>
13
14QT_BEGIN_NAMESPACE
15
16// Text color and style categories
17enum TextStyle : quint8 {
18 C_VISUAL_WHITESPACE,
19 C_PREPROCESSOR,
20 C_NUMBER,
21 C_COMMENT,
22 C_KEYWORD,
23 C_PARAMETER,
24 C_REMOVED_LINE
25};
26
27static constexpr TextStyle GLSLReservedKeyword = C_REMOVED_LINE;
28
29static QTextCharFormat formatForCategory(TextStyle category)
30{
31 switch (category) {
32 case C_PARAMETER:
33 {
34 QTextCharFormat fmt;
35 fmt.setForeground(QColor::fromRgb(r: 0xaa, g: 0xaa, b: 0xff));
36 return fmt;
37 }
38 case C_PREPROCESSOR:
39 {
40 QTextCharFormat fmt;
41 fmt.setForeground(QColor::fromRgb(r: 0x55, g: 0x55, b: 0xff));
42 return fmt;
43 }
44 case C_NUMBER:
45 {
46 QTextCharFormat fmt;
47 fmt.setForeground(QColor::fromRgb(r: 0xff, g: 0x55, b: 0xff));
48 return fmt;
49 }
50 case C_COMMENT:
51 {
52 QTextCharFormat fmt;
53 fmt.setForeground(QColor::fromRgb(r: 0x55, g: 0xff, b: 0xff));
54 return fmt;
55 }
56 case C_KEYWORD:
57 {
58 QTextCharFormat fmt;
59 fmt.setForeground(QColor::fromRgb(r: 0xff, g: 0xff, b: 0x55));
60 return fmt;
61 }
62 case C_REMOVED_LINE:
63 Q_FALLTHROUGH();
64 case C_VISUAL_WHITESPACE:
65 break;
66 }
67
68 return QTextCharFormat();
69}
70
71SyntaxHighlighter::SyntaxHighlighter(QObject *p)
72 : QSyntaxHighlighter(p)
73{
74
75}
76
77void SyntaxHighlighter::highlightBlock(const QString &text)
78{
79 if (m_keywords.isEmpty()) {
80 const auto keywords = QtQuick3DEditorHelpers::CustomMaterial::preprocessorVars();
81 for (const auto &kw : keywords)
82 m_keywords.insert(value: kw);
83 }
84 if (m_argumentKeywords.isEmpty()) {
85 const auto args = QtQuick3DEditorHelpers::CustomMaterial::reservedArgumentNames();
86 for (const auto &arg : args)
87 m_argumentKeywords.insert(value: arg);
88 }
89 const int previousState = previousBlockState();
90 int state = 0;
91 if (previousState != -1)
92 state = previousState & 0xff;
93
94 const QByteArray data = text.toLatin1();
95 GLSL::Lexer lex(/*engine=*/ nullptr, data.constData(), data.size());
96 lex.setState(state);
97 lex.setScanKeywords(false);
98 lex.setScanComments(true);
99
100 lex.setVariant(GLSL::Lexer::Variant_GLSL_400);
101
102 QList<GLSL::Token> tokens;
103 GLSL::Token tk;
104 do {
105 lex.yylex(tk: &tk);
106 tokens.append(t: tk);
107 } while (tk.isNot(k: GLSL::Parser::EOF_SYMBOL));
108
109 state = lex.state(); // refresh the state
110
111 if (tokens.isEmpty()) {
112 setCurrentBlockState(previousState);
113 if (!text.isEmpty()) // the empty line can still contain whitespace
114 setFormat(start: 0, count: text.size(), format: formatForCategory(category: C_VISUAL_WHITESPACE));
115 return;
116 }
117
118 for (int i = 0, end = tokens.size(); i != end; ++i) {
119 const GLSL::Token &tk = tokens.at(i);
120
121 int previousTokenEnd = 0;
122 if (i != 0) {
123 // mark the whitespaces
124 previousTokenEnd = tokens.at(i: i - 1).begin() + tokens.at(i: i - 1).length;
125 }
126
127 if (previousTokenEnd != tk.begin())
128 setFormat(start: previousTokenEnd, count: tk.begin() - previousTokenEnd, format: formatForCategory(category: C_VISUAL_WHITESPACE));
129
130 if (tk.is(k: GLSL::Parser::T_NUMBER)) {
131 setFormat(start: tk.begin(), count: tk.length, format: formatForCategory(category: C_NUMBER));
132 } else if (tk.is(k: GLSL::Parser::T_COMMENT)) {
133 highlightLine(text, position: tk.begin(), length: tk.length, format: formatForCategory(category: C_COMMENT));
134 } else if (tk.is(k: GLSL::Parser::T_IDENTIFIER)) {
135 int kind = lex.findKeyword(word: data.constData() + tk.position, length: tk.length);
136 if (kind == GLSL::Parser::T_IDENTIFIER) {
137 if (m_keywords.contains(value: QByteArrayView(data.constData() + tk.position, tk.length)))
138 setFormat(start: tk.position, count: tk.length, format: formatForCategory(category: C_PREPROCESSOR));
139 if (m_argumentKeywords.contains(value: QByteArrayView(data.constData() + tk.position, tk.length)))
140 setFormat(start: tk.position, count: tk.length, format: formatForCategory(category: C_PARAMETER));
141 }
142 if (kind == GLSL::Parser::T_RESERVED)
143 setFormat(start: tk.position, count: tk.length, format: formatForCategory(category: GLSLReservedKeyword));
144 else if (kind != GLSL::Parser::T_IDENTIFIER)
145 setFormat(start: tk.position, count: tk.length, format: formatForCategory(category: C_KEYWORD));
146 }
147 }
148
149 // mark the trailing white spaces
150 {
151 const GLSL::Token tk = tokens.last();
152 const int lastTokenEnd = tk.begin() + tk.length;
153 if (text.size() > lastTokenEnd)
154 highlightLine(text, position: lastTokenEnd, length: text.size() - lastTokenEnd, format: QTextCharFormat());
155 }
156}
157
158void SyntaxHighlighter::highlightLine(const QString &text, int position, int length,
159 const QTextCharFormat &format)
160{
161 const QTextCharFormat visualSpaceFormat = formatForCategory(category: C_VISUAL_WHITESPACE);
162
163 const int end = position + length;
164 int index = position;
165
166 while (index != end) {
167 const bool isSpace = text.at(i: index).isSpace();
168 const int start = index;
169
170 do { ++index; }
171 while (index != end && text.at(i: index).isSpace() == isSpace);
172
173 const int tokenLength = index - start;
174 if (isSpace)
175 setFormat(start, count: tokenLength, format: visualSpaceFormat);
176 else if (format.isValid())
177 setFormat(start, count: tokenLength, format);
178 }
179}
180
181QQuickTextDocument *SyntaxHighlighter::document() const
182{
183 return m_quickTextDocument;
184}
185
186void SyntaxHighlighter::setDocument(QQuickTextDocument *newDocument)
187{
188 if (m_quickTextDocument == newDocument)
189 return;
190
191 m_quickTextDocument = newDocument;
192 QSyntaxHighlighter::setDocument(m_quickTextDocument != nullptr ? m_quickTextDocument->textDocument() : nullptr);
193
194 emit documentChanged();
195}
196
197QT_END_NAMESPACE
198

source code of qtquick3d/tools/materialeditor/syntaxhighlighter.cpp