1 | // Copyright (C) 2024 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #ifndef QQMLSEMANTICTOKENS_P_H |
5 | #define QQMLSEMANTICTOKENS_P_H |
6 | |
7 | // |
8 | // W A R N I N G |
9 | // ------------- |
10 | // |
11 | // This file is not part of the Qt API. It exists purely as an |
12 | // implementation detail. This header file may change from version to |
13 | // version without notice, or even be removed. |
14 | // |
15 | // We mean it. |
16 | // |
17 | |
18 | #include <QtLanguageServer/private/qlanguageserverspec_p.h> |
19 | #include <QtQmlDom/private/qqmldomitem_p.h> |
20 | #include <QtCore/qlist.h> |
21 | #include <QtCore/qmap.h> |
22 | |
23 | QT_BEGIN_NAMESPACE |
24 | |
25 | Q_DECLARE_LOGGING_CATEGORY(semanticTokens) |
26 | |
27 | namespace HighlightingUtils { |
28 | Q_NAMESPACE |
29 | |
30 | // Protocol agnostic highlighting kinds |
31 | // Use this enum while visiting dom tree to define the highlighting kinds for the semantic tokens |
32 | // Then map it to the protocol specific token types and modifiers |
33 | // This can be as much as detailed as needed |
34 | enum class QmlHighlightKind { |
35 | QmlKeyword, // Qml keyword |
36 | QmlType, // Qml type name |
37 | QmlImportId, // Qml import module name |
38 | QmlNamespace, // Qml module namespace, i.e import QtQuick as Namespace |
39 | QmlLocalId, // Object id within the same file |
40 | QmlExternalId, // Object id defined in another file. [UNUSED FOR NOW] |
41 | QmlProperty, // Qml property. For now used for all kind of properties |
42 | QmlScopeObjectProperty, // Qml property defined in the current scope |
43 | QmlRootObjectProperty, // Qml property defined in the parent scopes |
44 | QmlExternalObjectProperty, // Qml property defined in the root object of another file |
45 | QmlMethod, |
46 | QmlMethodParameter, |
47 | QmlSignal, |
48 | QmlSignalHandler, |
49 | QmlEnumName, // Enum type name |
50 | QmlEnumMember, // Enum field names |
51 | QmlPragmaName, // Qml pragma name |
52 | QmlPragmaValue, // Qml pragma value |
53 | QmlTypeModifier, // list<QtObject>, list is the modifier, QtObject is the type |
54 | JsImport, // Js imported name |
55 | JsGlobalVar, // Js global variable or objects |
56 | JsGlobalMethod, // Js global method |
57 | JsScopeVar, // Js variable defined in the current scope |
58 | JsLabel, // js label |
59 | Number, |
60 | String, |
61 | , |
62 | Operator, |
63 | Unknown, // Used for the unknown tokens |
64 | }; |
65 | |
66 | enum class QmlHighlightModifier { |
67 | None = 0, |
68 | QmlPropertyDefinition = 1 << 0, |
69 | QmlDefaultProperty = 1 << 1, |
70 | QmlRequiredProperty = 1 << 2, |
71 | QmlReadonlyProperty = 1 << 3, |
72 | }; |
73 | Q_DECLARE_FLAGS(QmlHighlightModifiers, QmlHighlightModifier) |
74 | Q_DECLARE_OPERATORS_FOR_FLAGS(QmlHighlightModifiers) |
75 | |
76 | enum class HighlightingMode { Default, QtCHighlighting }; |
77 | |
78 | // Protocol specific token types |
79 | // The values in this enum are converted to relevant strings and sent to the client as server |
80 | // capabilities The convention is that the first letter in the enum value is decapitalized and the |
81 | // rest is unchanged i.e Namespace -> "namespace" This is handled in enumToByteArray() helper |
82 | // function. |
83 | enum class SemanticTokenProtocolTypes { |
84 | // Subset of the QLspSpefication::SemanticTokenTypes enum |
85 | // We register only the token types used in the qml semantic highlighting |
86 | Namespace, |
87 | Type, |
88 | Enum, |
89 | Parameter, |
90 | Variable, |
91 | Property, |
92 | EnumMember, |
93 | Method, |
94 | Keyword, |
95 | , |
96 | String, |
97 | Number, |
98 | Regexp, |
99 | Operator, |
100 | Decorator, |
101 | |
102 | // Additional token types for the extended semantic highlighting |
103 | QmlLocalId, // object id within the same file |
104 | QmlExternalId, // object id defined in another file |
105 | QmlRootObjectProperty, // qml property defined in the parent scopes |
106 | QmlScopeObjectProperty, // qml property defined in the current scope |
107 | QmlExternalObjectProperty, // qml property defined in the root object of another file |
108 | JsScopeVar, // js variable defined in the current file |
109 | JsImportVar, // js import name that is imported in the qml file |
110 | JsGlobalVar, // js global variables |
111 | QmlStateName, // name of a qml state |
112 | }; |
113 | Q_ENUM_NS(SemanticTokenProtocolTypes) |
114 | |
115 | } // namespace HighlightingUtils |
116 | |
117 | // Represents a semantic highlighting token |
118 | // startLine and startColumn are 0-based as in LSP spec. |
119 | struct Token |
120 | { |
121 | Token() = default; |
122 | Token(const QQmlJS::SourceLocation &loc, int tokenType, int tokenModifier = 0) |
123 | : offset(loc.offset), |
124 | length(loc.length), |
125 | startLine(loc.startLine - 1), |
126 | startColumn(loc.startColumn - 1), |
127 | tokenType(tokenType), |
128 | tokenModifier(tokenModifier) |
129 | { |
130 | } |
131 | |
132 | inline friend bool operator<(const Token &lhs, const Token &rhs) |
133 | { |
134 | return lhs.offset < rhs.offset; |
135 | } |
136 | |
137 | inline friend bool operator==(const Token &lhs, const Token &rhs) |
138 | { |
139 | return lhs.offset == rhs.offset && lhs.length == rhs.length |
140 | && lhs.startLine == rhs.startLine && lhs.startColumn == rhs.startColumn |
141 | && lhs.tokenType == rhs.tokenType && lhs.tokenModifier == rhs.tokenModifier; |
142 | } |
143 | |
144 | int offset; |
145 | int length; |
146 | int startLine; |
147 | int startColumn; |
148 | int tokenType; |
149 | int tokenModifier; |
150 | }; |
151 | |
152 | using HighlightsContainer = QMap<int, QT_PREPEND_NAMESPACE(Token)>; |
153 | |
154 | /*! |
155 | \internal |
156 | Offsets start from zero. |
157 | */ |
158 | struct HighlightsRange |
159 | { |
160 | int startOffset; |
161 | int endOffset; |
162 | }; |
163 | |
164 | class Highlights |
165 | { |
166 | public: |
167 | using QmlHighlightKindToLspKind = int (*)(HighlightingUtils::QmlHighlightKind); |
168 | Highlights(HighlightingUtils::HighlightingMode mode = HighlightingUtils::HighlightingMode::Default); |
169 | void addHighlight(const QQmlJS::SourceLocation &loc, HighlightingUtils::QmlHighlightKind, |
170 | HighlightingUtils::QmlHighlightModifiers = |
171 | HighlightingUtils::QmlHighlightModifier::None); |
172 | HighlightsContainer &highlights() { return m_highlights; } |
173 | const HighlightsContainer &highlights() const { return m_highlights; } |
174 | |
175 | private: |
176 | void addHighlightImpl(const QQmlJS::SourceLocation &loc, int tokenType, int tokenModifier = 0); |
177 | HighlightsContainer m_highlights; |
178 | QmlHighlightKindToLspKind m_mapToProtocol; |
179 | }; |
180 | |
181 | namespace HighlightingUtils |
182 | { |
183 | QList<int> encodeSemanticTokens(Highlights &highlights); |
184 | QList<QQmlJS::SourceLocation> |
185 | sourceLocationsFromMultiLineToken(QStringView code, |
186 | const QQmlJS::SourceLocation &tokenLocation); |
187 | void addModifier(QLspSpecification::SemanticTokenModifiers modifier, int *baseModifier); |
188 | bool rangeOverlapsWithSourceLocation(const QQmlJS::SourceLocation &loc, const HighlightsRange &r); |
189 | QList<QLspSpecification::SemanticTokensEdit> computeDiff(const QList<int> &, const QList<int> &); |
190 | void updateResultID(QByteArray &resultID); |
191 | QList<int> collectTokens(const QQmlJS::Dom::DomItem &item, |
192 | const std::optional<HighlightsRange> &range, |
193 | HighlightingMode mode = HighlightingMode::Default); |
194 | } // namespace HighlightingUtils |
195 | |
196 | class HighlightingVisitor |
197 | { |
198 | public: |
199 | HighlightingVisitor(Highlights &highlights, const std::optional<HighlightsRange> &range); |
200 | bool operator()(QQmlJS::Dom::Path, const QQmlJS::Dom::DomItem &item, bool); |
201 | |
202 | private: |
203 | void (const QQmlJS::Dom::DomItem &item); |
204 | void highlightImport(const QQmlJS::Dom::DomItem &item); |
205 | void highlightBinding(const QQmlJS::Dom::DomItem &item); |
206 | void highlightPragma(const QQmlJS::Dom::DomItem &item); |
207 | void highlightEnumItem(const QQmlJS::Dom::DomItem &item); |
208 | void highlightEnumDecl(const QQmlJS::Dom::DomItem &item); |
209 | void highlightQmlObject(const QQmlJS::Dom::DomItem &item); |
210 | void highlightComponent(const QQmlJS::Dom::DomItem &item); |
211 | void highlightPropertyDefinition(const QQmlJS::Dom::DomItem &item); |
212 | void highlightMethod(const QQmlJS::Dom::DomItem &item); |
213 | void highlightScriptLiteral(const QQmlJS::Dom::DomItem &item); |
214 | void highlightIdentifier(const QQmlJS::Dom::DomItem &item); |
215 | void highlightBySemanticAnalysis(const QQmlJS::Dom::DomItem &item, QQmlJS::SourceLocation loc); |
216 | void highlightScriptExpressions(const QQmlJS::Dom::DomItem &item); |
217 | |
218 | private: |
219 | Highlights &m_highlights; |
220 | std::optional<HighlightsRange> m_range; |
221 | }; |
222 | |
223 | QT_END_NAMESPACE |
224 | |
225 | #endif // QQMLSEMANTICTOKENS_P_H |
226 | |