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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | // Text color and style categories |
17 | enum TextStyle : quint8 { |
18 | C_VISUAL_WHITESPACE, |
19 | C_PREPROCESSOR, |
20 | C_NUMBER, |
21 | , |
22 | C_KEYWORD, |
23 | C_PARAMETER, |
24 | C_REMOVED_LINE |
25 | }; |
26 | |
27 | static constexpr TextStyle GLSLReservedKeyword = C_REMOVED_LINE; |
28 | |
29 | static 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 | |
71 | SyntaxHighlighter::SyntaxHighlighter(QObject *p) |
72 | : QSyntaxHighlighter(p) |
73 | { |
74 | |
75 | } |
76 | |
77 | void 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 | |
158 | void 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 | |
181 | QQuickTextDocument *SyntaxHighlighter::document() const |
182 | { |
183 | return m_quickTextDocument; |
184 | } |
185 | |
186 | void 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 | |
197 | QT_END_NAMESPACE |
198 | |