载入中...
搜索中...
未找到
LanguageIndex.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cctype>
7#include <cstdint>
8#include <unordered_map>
9#include <unordered_set>
10
11namespace eve::dev::lsp {
12namespace {
13
14enum class TokenKind { Identifier, String, Symbol };
15
16struct Token {
17 TokenKind kind = TokenKind::Symbol;
18 std::string text;
19 size_t offset = 0;
20 Range range;
21};
22
23struct ImportBinding {
24 std::string imported;
25 std::string local;
26 std::string targetUri;
27 size_t importedToken = 0;
28 size_t localToken = 0;
29 bool namespaceImport = false;
30};
31
32bool identifierStart(char value) { return std::isalpha(static_cast<unsigned char>(value)) != 0 || value == '_'; }
33
34bool identifierPart(char value) { return std::isalnum(static_cast<unsigned char>(value)) != 0 || value == '_'; }
35
36bool validIdentifier(std::string_view value) {
37 if (value.empty() || !identifierStart(value.front())) return false;
38 return std::all_of(value.begin() + 1, value.end(), identifierPart);
39}
40
41std::vector<Token> tokenize(std::string_view source) {
42 std::vector<Token> result;
43 size_t line = 0;
44 size_t column = 0;
45 const auto advance = [&](char value, size_t& currentLine, size_t& currentColumn) {
46 if (value == '\n') {
47 ++currentLine;
48 currentColumn = 0;
49 } else {
50 ++currentColumn;
51 }
52 };
53
54 for (size_t i = 0; i < source.size();) {
55 const char value = source[i];
56 const char next = i + 1 < source.size() ? source[i + 1] : 0;
57 if (std::isspace(static_cast<unsigned char>(value)) != 0) {
58 advance(value, line, column);
59 ++i;
60 continue;
61 }
62 if (value == '/' && next == '/') {
63 while (i < source.size() && source[i] != '\n') {
64 ++i;
65 ++column;
66 }
67 continue;
68 }
69 if (value == '/' && next == '*') {
70 advance(value, line, column);
71 advance(next, line, column);
72 i += 2;
73 while (i < source.size()) {
74 const char current = source[i];
75 if (current == '*' && i + 1 < source.size() && source[i + 1] == '/') {
76 advance(current, line, column);
77 advance('/', line, column);
78 i += 2;
79 break;
80 }
81 advance(current, line, column);
82 ++i;
83 }
84 continue;
85 }
86
87 const size_t tokenOffset = i;
88 const Position start{line, column};
89 if (identifierStart(value)) {
90 ++i;
91 ++column;
92 while (i < source.size() && identifierPart(source[i])) {
93 ++i;
94 ++column;
95 }
96 result.push_back({TokenKind::Identifier,
97 std::string(source.substr(tokenOffset, i - tokenOffset)),
98 tokenOffset,
99 {start, {line, column}}});
100 continue;
101 }
102
103 const bool verbatim = value == '@' && (next == '"' || next == '\'');
104 if (value == '"' || value == '\'' || verbatim) {
105 const char quote = verbatim ? next : value;
106 if (verbatim) {
107 i += 2;
108 column += 2;
109 } else {
110 ++i;
111 ++column;
112 }
113 std::string text;
114 while (i < source.size()) {
115 const char current = source[i];
116 if (!verbatim && current == '\\' && i + 1 < source.size()) {
117 text += source[i + 1];
118 advance(current, line, column);
119 ++i;
120 advance(source[i], line, column);
121 ++i;
122 continue;
123 }
124 if (current == quote) {
125 if (verbatim && i + 1 < source.size() && source[i + 1] == quote) {
126 text += quote;
127 i += 2;
128 column += 2;
129 continue;
130 }
131 ++i;
132 ++column;
133 break;
134 }
135 text += current;
136 advance(current, line, column);
137 ++i;
138 }
139 result.push_back({TokenKind::String, std::move(text), tokenOffset, {start, {line, column}}});
140 continue;
141 }
142
143 ++i;
144 ++column;
145 result.push_back({TokenKind::Symbol, std::string(1, value), tokenOffset, {start, {line, column}}});
146 }
147 return result;
148}
149
150std::string resolveImport(std::string_view importer, std::string_view specifier) {
151 script::ScriptModuleRequest request{std::string(importer), std::string(specifier)};
152 std::string canonical;
153 std::string error;
154 return script::ScriptModuleResolver::canonicalize(request, canonical, error) ? canonical : std::string{};
155}
156
157bool contains(const Range& range, Position position) {
158 if (position.line < range.start.line || position.line > range.end.line) return false;
159 if (position.line == range.start.line && position.character < range.start.character) return false;
160 if (position.line == range.end.line && position.character > range.end.character) return false;
161 return true;
162}
163
164bool isKeyword(std::string_view word) {
165 static const std::unordered_set<std::string> words = {
166 "if", "else", "switch", "case", "default", "while", "do", "for",
167 "foreach", "break", "continue","return", "try", "catch", "throw", "yield",
168 "resume", "await", "local", "const", "enum", "class", "function", "constructor",
169 "static", "persist","async", "export", "import", "from", "as", "extends",
170 "in", "typeof", "instanceof","clone", "delete", "this", "base", "self",
171 "true", "false", "null", "nullrel"};
172 return words.count(std::string(word)) != 0;
173}
174
175} // namespace
176
178 std::string canonicalUri;
179 std::string clientUri;
180 std::string source;
181 std::vector<Token> tokens;
182 std::unordered_map<std::string, size_t> declarations;
183 std::unordered_map<std::string, size_t> exports;
184 std::vector<ImportBinding> imports;
185 std::unordered_map<std::string, uint32_t> nameTypes;
186 std::unordered_map<std::string, uint32_t> nameModifiers;
187};
188
190 std::string definingUri;
191 std::string name;
192 std::string localUri;
193 std::string localName;
194 bool localAlias = false;
195};
196
198 std::unordered_map<std::string, Unit> units;
199};
200
201WorkspaceIndex::WorkspaceIndex() : impl_(std::make_unique<Impl>()) {}
203
204void WorkspaceIndex::update(std::string canonicalUri, std::string clientUri, std::string source) {
205 Unit next;
206 next.canonicalUri = std::move(canonicalUri);
207 next.clientUri = std::move(clientUri);
208 next.source = std::move(source);
209 next.tokens = tokenize(next.source);
210
211 const auto& tokens = next.tokens;
212 for (size_t i = 0; i < tokens.size(); ++i) {
213 if (tokens[i].kind != TokenKind::Identifier) continue;
214 const std::string& word = tokens[i].text;
215 const auto remember = [&](const std::string& name, uint32_t type, uint32_t modifiers) {
216 next.nameTypes.try_emplace(name, type);
217 next.nameModifiers.try_emplace(name, modifiers);
218 };
219 if ((word == "local" || word == "function" || word == "class" || word == "const" || word == "persist") &&
220 i + 1 < tokens.size() && tokens[i + 1].kind == TokenKind::Identifier &&
221 !isKeyword(tokens[i + 1].text)) {
222 next.declarations.try_emplace(tokens[i + 1].text, i + 1);
223 if (word == "class")
224 remember(tokens[i + 1].text, SemanticTypes::Class, 0);
225 else if (word == "function")
226 remember(tokens[i + 1].text, SemanticTypes::Function, 0);
227 else if (word == "const")
229 else
230 remember(tokens[i + 1].text, SemanticTypes::Variable, 0);
231 }
232 if (word == "export" && i + 2 < tokens.size() && tokens[i + 1].kind == TokenKind::Identifier &&
233 (tokens[i + 1].text == "function" || tokens[i + 1].text == "class" || tokens[i + 1].text == "const") &&
234 tokens[i + 2].kind == TokenKind::Identifier) {
235 next.declarations.try_emplace(tokens[i + 2].text, i + 2);
236 next.exports[tokens[i + 2].text] = i + 2;
237 if (tokens[i + 1].text == "class")
238 remember(tokens[i + 2].text, SemanticTypes::Class, 0);
239 else if (tokens[i + 1].text == "function")
240 remember(tokens[i + 2].text, SemanticTypes::Function, 0);
241 else
243 }
244 if (word != "import" || i + 1 >= tokens.size()) continue;
245
246 size_t cursor = i + 1;
247 struct Pending {
248 std::string imported;
249 std::string local;
250 size_t importedToken;
251 size_t localToken;
252 bool ns;
253 };
254 std::vector<Pending> pending;
255 if (tokens[cursor].text == "{") {
256 ++cursor;
257 while (cursor < tokens.size() && tokens[cursor].text != "}") {
258 if (tokens[cursor].kind != TokenKind::Identifier) {
259 ++cursor;
260 continue;
261 }
262 Pending binding{tokens[cursor].text, tokens[cursor].text, cursor, cursor, false};
263 ++cursor;
264 if (cursor + 1 < tokens.size() && tokens[cursor].text == "as" &&
265 tokens[cursor + 1].kind == TokenKind::Identifier) {
266 binding.local = tokens[cursor + 1].text;
267 binding.localToken = cursor + 1;
268 cursor += 2;
269 }
270 pending.push_back(std::move(binding));
271 if (cursor < tokens.size() && tokens[cursor].text == ",") ++cursor;
272 }
273 if (cursor < tokens.size()) ++cursor;
274 } else if (tokens[cursor].text == "*" && cursor + 2 < tokens.size() && tokens[cursor + 1].text == "as" &&
275 tokens[cursor + 2].kind == TokenKind::Identifier) {
276 pending.push_back({"*", tokens[cursor + 2].text, cursor, cursor + 2, true});
277 cursor += 3;
278 }
279 if (pending.empty() || cursor + 1 >= tokens.size() || tokens[cursor].text != "from" ||
280 tokens[cursor + 1].kind != TokenKind::String)
281 continue;
282 const std::string target = resolveImport(next.canonicalUri, tokens[cursor + 1].text);
283 for (const Pending& binding : pending)
284 next.imports.push_back(
285 {binding.imported, binding.local, target, binding.importedToken, binding.localToken, binding.ns});
286 }
287 impl_->units[next.canonicalUri] = std::move(next);
288}
289
290void WorkspaceIndex::remove(std::string_view canonicalUri) { impl_->units.erase(std::string(canonicalUri)); }
291
292const WorkspaceIndex::Unit* WorkspaceIndex::unit(std::string_view canonicalUri) const {
293 const auto found = impl_->units.find(std::string(canonicalUri));
294 return found == impl_->units.end() ? nullptr : &found->second;
295}
296
297std::optional<WorkspaceIndex::Target> WorkspaceIndex::targetAt(const Unit& current, Position position) const {
298 size_t tokenIndex = current.tokens.size();
299 for (size_t i = 0; i < current.tokens.size(); ++i) {
300 if (current.tokens[i].kind == TokenKind::Identifier && contains(current.tokens[i].range, position)) {
301 tokenIndex = i;
302 break;
303 }
304 }
305 if (tokenIndex == current.tokens.size()) return std::nullopt;
306 const std::string& name = current.tokens[tokenIndex].text;
307 for (const ImportBinding& binding : current.imports) {
308 if (!binding.namespaceImport || tokenIndex < 2) continue;
309 if (current.tokens[tokenIndex - 2].kind == TokenKind::Identifier &&
310 current.tokens[tokenIndex - 2].text == binding.local && current.tokens[tokenIndex - 1].text == ".")
311 return Target{binding.targetUri, name, current.canonicalUri, name, false};
312 }
313 for (const ImportBinding& binding : current.imports) {
314 if (tokenIndex == binding.importedToken)
315 return Target{binding.targetUri, binding.imported, current.canonicalUri, binding.local, false};
316 if (name == binding.local) {
317 const bool alias = binding.local != binding.imported;
318 return Target{binding.targetUri, binding.imported, current.canonicalUri, binding.local, alias};
319 }
320 }
321 if (current.exports.find(name) != current.exports.end())
322 return Target{current.canonicalUri, name, current.canonicalUri, name, false};
323 if (current.declarations.find(name) != current.declarations.end())
324 return Target{current.canonicalUri, name, current.canonicalUri, name, true};
325 return std::nullopt;
326}
327
328std::optional<Location> WorkspaceIndex::targetDefinition(const Target& target) const {
329 const Unit* defining = unit(target.definingUri);
330 if (defining == nullptr) return std::nullopt;
331 const auto exported = defining->exports.find(target.name);
332 const auto declared = defining->declarations.find(target.name);
333 if (exported != defining->exports.end())
334 return Location{defining->clientUri, defining->tokens[exported->second].range};
335 if (declared != defining->declarations.end())
336 return Location{defining->clientUri, defining->tokens[declared->second].range};
337 return std::nullopt;
338}
339
340std::vector<Location> WorkspaceIndex::targetReferences(const Target& target, bool includeDeclaration) const {
341 std::vector<Location> result;
342 const auto appendNamed = [&](const Unit& source, std::string_view name, bool declaration) {
343 std::unordered_set<size_t> declarationTokens;
344 for (const auto& [_, token] : source.declarations) declarationTokens.insert(token);
345 for (size_t i = 0; i < source.tokens.size(); ++i) {
346 if (source.tokens[i].kind != TokenKind::Identifier || source.tokens[i].text != name) continue;
347 const bool isDeclaration = declarationTokens.count(i) != 0;
348 if (!declaration && isDeclaration) continue;
349 if (!includeDeclaration && isDeclaration) continue;
350 result.push_back({source.clientUri, source.tokens[i].range});
351 }
352 };
353
354 if (target.localAlias) {
355 if (const Unit* local = unit(target.localUri)) appendNamed(*local, target.localName, true);
356 } else {
357 if (const Unit* defining = unit(target.definingUri)) appendNamed(*defining, target.name, true);
358 for (const auto& [_, source] : impl_->units) {
359 for (const ImportBinding& binding : source.imports) {
360 if (binding.targetUri != target.definingUri) continue;
361 if (binding.namespaceImport) {
362 for (size_t i = 0; i + 2 < source.tokens.size(); ++i) {
363 if (source.tokens[i].kind != TokenKind::Identifier || source.tokens[i].text != binding.local ||
364 source.tokens[i + 1].text != "." || source.tokens[i + 2].kind != TokenKind::Identifier ||
365 source.tokens[i + 2].text != target.name)
366 continue;
367 result.push_back({source.clientUri, source.tokens[i + 2].range});
368 }
369 continue;
370 }
371 if (binding.imported != target.name) continue;
372 result.push_back({source.clientUri, source.tokens[binding.importedToken].range});
373 if (binding.local != binding.imported) continue;
374 for (size_t i = 0; i < source.tokens.size(); ++i) {
375 if (source.tokens[i].kind != TokenKind::Identifier || source.tokens[i].text != binding.local)
376 continue;
377 result.push_back({source.clientUri, source.tokens[i].range});
378 }
379 }
380 }
381 }
382 std::sort(result.begin(), result.end(), [](const Location& a, const Location& b) {
383 if (a.uri != b.uri) return a.uri < b.uri;
384 if (a.range.start.line != b.range.start.line) return a.range.start.line < b.range.start.line;
385 return a.range.start.character < b.range.start.character;
386 });
387 result.erase(std::unique(result.begin(), result.end(),
388 [](const Location& a, const Location& b) {
389 return a.uri == b.uri && a.range.start.line == b.range.start.line &&
390 a.range.start.character == b.range.start.character;
391 }),
392 result.end());
393 return result;
394}
395
396std::vector<SemanticToken> WorkspaceIndex::semanticTokens(std::string_view canonicalUri) const {
397 const Unit* current = unit(canonicalUri);
398 if (current == nullptr) return {};
399 const auto& tokens = current->tokens;
400 std::vector<SemanticToken> result;
401 for (size_t i = 0; i < tokens.size(); ++i) {
402 if (tokens[i].kind != TokenKind::Identifier) continue;
403 const std::string& name = tokens[i].text;
404 if (name == "true" || name == "false" || name == "null" || name == "nullrel" || name == "this" ||
405 name == "base" || name == "self") {
406 result.push_back({tokens[i].range.start, name.size(), SemanticTypes::Keyword, 0, name});
407 continue;
408 }
409 if (isKeyword(name)) continue;
410 const Token* prev = i > 0 ? &tokens[i - 1] : nullptr;
411 const Token* next = i + 1 < tokens.size() ? &tokens[i + 1] : nullptr;
412 const bool called = next && next->text == "(";
413 const bool member = prev && prev->text == ".";
415 token.start = tokens[i].range.start;
416 token.length = name.size();
417 token.name = name;
418 token.type = SemanticTypes::Variable;
419 token.modifiers = 0;
420
421 const std::string prevText = prev && prev->kind == TokenKind::Identifier ? prev->text : std::string{};
422 if (prevText == "class" || prevText == "extends" || (prev && prev->text == ":")) {
423 token.type = SemanticTypes::Class;
424 token.modifiers = prevText == "class" ? SemanticMods::Declaration : 0;
425 } else if (prevText == "function") {
426 token.type = SemanticTypes::Function;
427 token.modifiers = SemanticMods::Declaration;
428 } else if (prevText == "local" || prevText == "persist") {
429 token.type = SemanticTypes::Variable;
430 token.modifiers = SemanticMods::Declaration;
431 } else if (prevText == "const") {
432 token.type = SemanticTypes::Variable;
433 token.modifiers = SemanticMods::Declaration | SemanticMods::Readonly;
434 } else if (member && called) {
435 token.type = (!name.empty() && std::isupper(static_cast<unsigned char>(name.front())) != 0)
436 ? SemanticTypes::Class
437 : SemanticTypes::Method;
438 } else if (member) {
439 token.type = SemanticTypes::Property;
440 } else if (called) {
441 const auto found = current->nameTypes.find(name);
442 if (found != current->nameTypes.end() && found->second == SemanticTypes::Class)
443 token.type = SemanticTypes::Class;
444 else if (!name.empty() && std::isupper(static_cast<unsigned char>(name.front())) != 0)
445 token.type = SemanticTypes::Class;
446 else
447 token.type = SemanticTypes::Function;
448 } else {
449 const auto found = current->nameTypes.find(name);
450 if (found != current->nameTypes.end()) token.type = found->second;
451 const auto mods = current->nameModifiers.find(name);
452 if (mods != current->nameModifiers.end()) token.modifiers = mods->second;
453 }
454 result.push_back(std::move(token));
455 }
456 return result;
457}
458
459std::optional<Location> WorkspaceIndex::definition(std::string_view canonicalUri, Position position) const {
460 const Unit* current = unit(canonicalUri);
461 if (current == nullptr) return std::nullopt;
462 const auto target = targetAt(*current, position);
463 return target ? targetDefinition(*target) : std::nullopt;
464}
465
466std::vector<Location> WorkspaceIndex::references(std::string_view canonicalUri, Position position,
467 bool includeDeclaration) const {
468 const Unit* current = unit(canonicalUri);
469 if (current == nullptr) return {};
470 const auto target = targetAt(*current, position);
471 return target ? targetReferences(*target, includeDeclaration) : std::vector<Location>{};
472}
473
474std::optional<std::vector<TextEdit>> WorkspaceIndex::rename(std::string_view canonicalUri, Position position,
475 std::string_view newName) const {
476 if (!validIdentifier(newName)) return std::nullopt;
477 const Unit* current = unit(canonicalUri);
478 if (current == nullptr) return std::nullopt;
479 const auto target = targetAt(*current, position);
480 if (!target || !targetDefinition(*target)) return std::nullopt;
481 std::vector<TextEdit> edits;
482 for (const Location& location : targetReferences(*target, true)) edits.push_back({location, std::string(newName)});
483 return edits.empty() ? std::nullopt : std::optional<std::vector<TextEdit>>(std::move(edits));
484}
485
486} // namespace eve::dev::lsp
LogicalId target
double value
Duration start
std::vector< QuestEvent > pending
int column
const GltfImportRequest & request
std::unordered_map< const Graphics *, std::shared_ptr< Lifetime > > tokens
bool namespaceImport
std::string text
TokenKind kind
size_t importedToken
size_t localToken
std::string local
std::string targetUri
size_t offset
std::string imported
Range range
bool verbatim
char quote
std::array< float, 3 > position
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::string error
Definition Package.cpp:60
eve::action::ActionVfxBinding binding
uint64_t token
bool found
double current
TacticalUnit * unit
std::vector< UnitCandidate > units
std::size_t cursor
float(ui::Theme::* member)[4]
const UnitySourceAsset & source
~WorkspaceIndex()
Workspace index.
void update(std::string canonicalUri, std::string clientUri, std::string source)
Adds or replaces one canonical source unit.
static bool canonicalize(const ScriptModuleRequest &request, std::string &output, std::string &error)
Normalizes a logical module URI and rejects root traversal.
std::string word
constexpr uint32_t Readonly
constexpr uint32_t Variable
constexpr uint32_t Function
constexpr HexDirection next(HexDirection d) noexcept
The next direction clockwise (NW wraps to NE).
Definition HexMetrics.h:76
enum EVENGINE_API_FOUNDATION String
OT_STRING.
Definition Runtime.h:119
Location public API.
Position public API.
One identifier classified for editor semantic highlighting.
std::unordered_map< std::string, Unit > units
std::unordered_map< std::string, uint32_t > nameTypes
std::unordered_map< std::string, size_t > exports
std::unordered_map< std::string, uint32_t > nameModifiers
std::unordered_map< std::string, size_t > declarations
std::vector< ImportBinding > imports