@velarscript/cli 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +117 -0
- package/dist/application-package-host.d.ts +5 -0
- package/dist/application-package-host.d.ts.map +1 -0
- package/dist/application-package-host.js +67 -0
- package/dist/application-package-host.js.map +1 -0
- package/dist/bounded-text.d.ts +2 -0
- package/dist/bounded-text.d.ts.map +1 -0
- package/dist/bounded-text.js +13 -0
- package/dist/bounded-text.js.map +1 -0
- package/dist/browser-process-owner.d.ts +15 -0
- package/dist/browser-process-owner.d.ts.map +1 -0
- package/dist/browser-process-owner.js +151 -0
- package/dist/browser-process-owner.js.map +1 -0
- package/dist/browser-test-runner.d.ts +11 -0
- package/dist/browser-test-runner.d.ts.map +1 -0
- package/dist/browser-test-runner.js +1022 -0
- package/dist/browser-test-runner.js.map +1 -0
- package/dist/build-engine-tool.d.ts +3 -0
- package/dist/build-engine-tool.d.ts.map +1 -0
- package/dist/build-engine-tool.js +34 -0
- package/dist/build-engine-tool.js.map +1 -0
- package/dist/build-staging.d.ts +3 -0
- package/dist/build-staging.d.ts.map +1 -0
- package/dist/build-staging.js +3 -0
- package/dist/build-staging.js.map +1 -0
- package/dist/bundled-application-package-host-registry.d.ts +10 -0
- package/dist/bundled-application-package-host-registry.d.ts.map +1 -0
- package/dist/bundled-application-package-host-registry.js +16 -0
- package/dist/bundled-application-package-host-registry.js.map +1 -0
- package/dist/bundled-extension-registry.d.ts +22 -0
- package/dist/bundled-extension-registry.d.ts.map +1 -0
- package/dist/bundled-extension-registry.js +10 -0
- package/dist/bundled-extension-registry.js.map +1 -0
- package/dist/canonical-path.d.ts +3 -0
- package/dist/canonical-path.d.ts.map +1 -0
- package/dist/canonical-path.js +30 -0
- package/dist/canonical-path.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +1274 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +26 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +403 -0
- package/dist/config.js.map +1 -0
- package/dist/deployment-verifier.d.ts +34 -0
- package/dist/deployment-verifier.d.ts.map +1 -0
- package/dist/deployment-verifier.js +260 -0
- package/dist/deployment-verifier.js.map +1 -0
- package/dist/dev-server.d.ts +3 -0
- package/dist/dev-server.d.ts.map +1 -0
- package/dist/dev-server.js +464 -0
- package/dist/dev-server.js.map +1 -0
- package/dist/embedded-modules.d.ts +17 -0
- package/dist/embedded-modules.d.ts.map +1 -0
- package/dist/embedded-modules.js +33 -0
- package/dist/embedded-modules.js.map +1 -0
- package/dist/extension-metadata.d.ts +23 -0
- package/dist/extension-metadata.d.ts.map +1 -0
- package/dist/extension-metadata.js +254 -0
- package/dist/extension-metadata.js.map +1 -0
- package/dist/file-integrity.d.ts +7 -0
- package/dist/file-integrity.d.ts.map +1 -0
- package/dist/file-integrity.js +14 -0
- package/dist/file-integrity.js.map +1 -0
- package/dist/framework-host.d.ts +12 -0
- package/dist/framework-host.d.ts.map +1 -0
- package/dist/framework-host.js +46 -0
- package/dist/framework-host.js.map +1 -0
- package/dist/host-error.d.ts +6 -0
- package/dist/host-error.d.ts.map +1 -0
- package/dist/host-error.js +54 -0
- package/dist/host-error.js.map +1 -0
- package/dist/installed-package.d.ts +9 -0
- package/dist/installed-package.d.ts.map +1 -0
- package/dist/installed-package.js +23 -0
- package/dist/installed-package.js.map +1 -0
- package/dist/language-server-bundle-entry.d.ts +2 -0
- package/dist/language-server-bundle-entry.d.ts.map +1 -0
- package/dist/language-server-bundle-entry.js +7 -0
- package/dist/language-server-bundle-entry.js.map +1 -0
- package/dist/language-server-tool.d.ts +3 -0
- package/dist/language-server-tool.d.ts.map +1 -0
- package/dist/language-server-tool.js +30 -0
- package/dist/language-server-tool.js.map +1 -0
- package/dist/language-server.d.ts +9 -0
- package/dist/language-server.d.ts.map +1 -0
- package/dist/language-server.js +1733 -0
- package/dist/language-server.js.map +1 -0
- package/dist/mechanical-fixer.d.ts +30 -0
- package/dist/mechanical-fixer.d.ts.map +1 -0
- package/dist/mechanical-fixer.js +103 -0
- package/dist/mechanical-fixer.js.map +1 -0
- package/dist/module-assets.d.ts +11 -0
- package/dist/module-assets.d.ts.map +1 -0
- package/dist/module-assets.js +90 -0
- package/dist/module-assets.js.map +1 -0
- package/dist/npm.d.ts +23 -0
- package/dist/npm.d.ts.map +1 -0
- package/dist/npm.js +456 -0
- package/dist/npm.js.map +1 -0
- package/dist/official-language-server-extensions.d.ts +2 -0
- package/dist/official-language-server-extensions.d.ts.map +1 -0
- package/dist/official-language-server-extensions.js +38 -0
- package/dist/official-language-server-extensions.js.map +1 -0
- package/dist/official-script-language-service.d.ts +2 -0
- package/dist/official-script-language-service.d.ts.map +1 -0
- package/dist/official-script-language-service.js +10 -0
- package/dist/official-script-language-service.js.map +1 -0
- package/dist/official-tool-assets.d.ts +3 -0
- package/dist/official-tool-assets.d.ts.map +1 -0
- package/dist/official-tool-assets.js +41 -0
- package/dist/official-tool-assets.js.map +1 -0
- package/dist/ownership-graph.d.ts +46 -0
- package/dist/ownership-graph.d.ts.map +1 -0
- package/dist/ownership-graph.js +255 -0
- package/dist/ownership-graph.js.map +1 -0
- package/dist/package-manager.d.ts +20 -0
- package/dist/package-manager.d.ts.map +1 -0
- package/dist/package-manager.js +345 -0
- package/dist/package-manager.js.map +1 -0
- package/dist/packaged-official-tool.d.ts +8 -0
- package/dist/packaged-official-tool.d.ts.map +1 -0
- package/dist/packaged-official-tool.js +32 -0
- package/dist/packaged-official-tool.js.map +1 -0
- package/dist/preview-server.d.ts +11 -0
- package/dist/preview-server.d.ts.map +1 -0
- package/dist/preview-server.js +163 -0
- package/dist/preview-server.js.map +1 -0
- package/dist/production-build.d.ts +57 -0
- package/dist/production-build.d.ts.map +1 -0
- package/dist/production-build.js +332 -0
- package/dist/production-build.js.map +1 -0
- package/dist/production-verifier.d.ts +9 -0
- package/dist/production-verifier.d.ts.map +1 -0
- package/dist/production-verifier.js +268 -0
- package/dist/production-verifier.js.map +1 -0
- package/dist/program-runner.d.ts +7 -0
- package/dist/program-runner.d.ts.map +1 -0
- package/dist/program-runner.js +98 -0
- package/dist/program-runner.js.map +1 -0
- package/dist/project-check.d.ts +31 -0
- package/dist/project-check.d.ts.map +1 -0
- package/dist/project-check.js +95 -0
- package/dist/project-check.js.map +1 -0
- package/dist/project-format.d.ts +6 -0
- package/dist/project-format.d.ts.map +1 -0
- package/dist/project-format.js +21 -0
- package/dist/project-format.js.map +1 -0
- package/dist/project-semantic.d.ts +62 -0
- package/dist/project-semantic.d.ts.map +1 -0
- package/dist/project-semantic.js +1079 -0
- package/dist/project-semantic.js.map +1 -0
- package/dist/project-session.d.ts +36 -0
- package/dist/project-session.d.ts.map +1 -0
- package/dist/project-session.js +339 -0
- package/dist/project-session.js.map +1 -0
- package/dist/project-task-bundle-entry.d.ts +2 -0
- package/dist/project-task-bundle-entry.d.ts.map +1 -0
- package/dist/project-task-bundle-entry.js +18 -0
- package/dist/project-task-bundle-entry.js.map +1 -0
- package/dist/project-task-invocation.d.ts +16 -0
- package/dist/project-task-invocation.d.ts.map +1 -0
- package/dist/project-task-invocation.js +72 -0
- package/dist/project-task-invocation.js.map +1 -0
- package/dist/project-task-tool.d.ts +3 -0
- package/dist/project-task-tool.d.ts.map +1 -0
- package/dist/project-task-tool.js +61 -0
- package/dist/project-task-tool.js.map +1 -0
- package/dist/project.d.ts +71 -0
- package/dist/project.d.ts.map +1 -0
- package/dist/project.js +1750 -0
- package/dist/project.js.map +1 -0
- package/dist/reproduction.d.ts +50 -0
- package/dist/reproduction.d.ts.map +1 -0
- package/dist/reproduction.js +321 -0
- package/dist/reproduction.js.map +1 -0
- package/dist/script-language-service.d.ts +82 -0
- package/dist/script-language-service.d.ts.map +1 -0
- package/dist/script-language-service.js +18 -0
- package/dist/script-language-service.js.map +1 -0
- package/dist/source-limits.d.ts +5 -0
- package/dist/source-limits.d.ts.map +1 -0
- package/dist/source-limits.js +19 -0
- package/dist/source-limits.js.map +1 -0
- package/dist/standalone-build.d.ts +15 -0
- package/dist/standalone-build.d.ts.map +1 -0
- package/dist/standalone-build.js +76 -0
- package/dist/standalone-build.js.map +1 -0
- package/dist/standard-modules.d.ts +22 -0
- package/dist/standard-modules.d.ts.map +1 -0
- package/dist/standard-modules.js +3066 -0
- package/dist/standard-modules.js.map +1 -0
- package/dist/static-deployment.d.ts +48 -0
- package/dist/static-deployment.d.ts.map +1 -0
- package/dist/static-deployment.js +142 -0
- package/dist/static-deployment.js.map +1 -0
- package/dist/test-output.d.ts +20 -0
- package/dist/test-output.d.ts.map +1 -0
- package/dist/test-output.js +119 -0
- package/dist/test-output.js.map +1 -0
- package/dist/test-runner.d.ts +13 -0
- package/dist/test-runner.d.ts.map +1 -0
- package/dist/test-runner.js +226 -0
- package/dist/test-runner.js.map +1 -0
- package/dist/typescript-declarations.d.ts +20 -0
- package/dist/typescript-declarations.d.ts.map +1 -0
- package/dist/typescript-declarations.js +1609 -0
- package/dist/typescript-declarations.js.map +1 -0
- package/dist/uncaught-program-error.d.ts +24 -0
- package/dist/uncaught-program-error.d.ts.map +1 -0
- package/dist/uncaught-program-error.js +105 -0
- package/dist/uncaught-program-error.js.map +1 -0
- package/dist/unowned-errors.d.ts +56 -0
- package/dist/unowned-errors.d.ts.map +1 -0
- package/dist/unowned-errors.js +82 -0
- package/dist/unowned-errors.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/workspace-index.d.ts +78 -0
- package/dist/workspace-index.d.ts.map +1 -0
- package/dist/workspace-index.js +493 -0
- package/dist/workspace-index.js.map +1 -0
- package/package.json +45 -0
- package/skill/ai-skill.md +749 -0
|
@@ -0,0 +1,1733 @@
|
|
|
1
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
3
|
+
import { compile, CORE_CONTEXTUAL_KEYWORD_WORDS, CORE_PRELUDE_NAMES, formatSource, isSourceIdentifierPart, keywordKinds, PERMANENT_NAMESPACE_NAMES, permanentNamespaceCoveringModule, } from "@velarscript/compiler";
|
|
4
|
+
import { standardModuleInterfaces } from "./standard-modules.js";
|
|
5
|
+
import { VelarProjectSessions } from "./project-session.js";
|
|
6
|
+
import { VELAR_VERSION } from "./version.js";
|
|
7
|
+
import { hostErrorMessage } from "./host-error.js";
|
|
8
|
+
import { canonicalizePotentialPath, canonicalPathWithinCanonicalRoot } from "./canonical-path.js";
|
|
9
|
+
import { createScriptLanguageDocument, scriptLanguageFor, } from "./script-language-service.js";
|
|
10
|
+
import { buildOwnershipGraph, ownershipGraphRevision } from "./ownership-graph.js";
|
|
11
|
+
import { projectDefinitionAt, projectCompletionsAt, projectCompletionContextAt, projectDocumentSymbols, projectExpressionAt, projectMemberSymbolAt, projectPrepareRenameAt, projectReferencesAt, projectRenameAt, projectSemanticTokens, projectSignatureAt, projectSymbolAt, projectWorkspaceSymbols, } from "./project-semantic.js";
|
|
12
|
+
import { MAX_WORKSPACE_SEARCH_RESULTS, MAX_WORKSPACE_TEXT_FILES, MAX_WORKSPACE_CHANGE_PATHS, MAX_WORKSPACE_CHANGE_PATH_CODE_UNITS, MAX_WORKSPACE_CHANGE_TEXT_CODE_UNITS, WORKSPACE_TEXT_EXTENSIONS, WorkspaceIndexCancelledError, WorkspaceTextIndex, } from "./workspace-index.js";
|
|
13
|
+
export const VELAR_LANGUAGE_SERVER_PROTOCOL_VERSION = 4;
|
|
14
|
+
let activePositionEncoding = "utf-16";
|
|
15
|
+
let confinedWorkspaceRoot = null;
|
|
16
|
+
let confinedCanonicalRoot = null;
|
|
17
|
+
const MAX_LSP_MESSAGE_BYTES = 16 * 1024 * 1024;
|
|
18
|
+
const MAX_LSP_RESULT_ITEMS = 10_000;
|
|
19
|
+
const MAX_LSP_TEXT_CHARS = 64 * 1024;
|
|
20
|
+
const MAX_EMITTED_JAVASCRIPT_CHARS = 4 * 1024 * 1024;
|
|
21
|
+
const MAX_OWNERSHIP_GRAPH_NODES = 20_000;
|
|
22
|
+
const MAX_OWNERSHIP_GRAPH_EDGES = 40_000;
|
|
23
|
+
const semanticTokenTypes = [
|
|
24
|
+
"type", "class", "enum", "enumMember", "function", "method", "property", "variable", "parameter",
|
|
25
|
+
"interface", "comment", "string", "keyword", "number", "regexp", "operator",
|
|
26
|
+
];
|
|
27
|
+
const semanticTokenModifiers = ["declaration", "readonly", "static"];
|
|
28
|
+
const keywordDocumentation = new Map([
|
|
29
|
+
["assert", "Requires a boolean or optional invariant and narrows stable values in following statements."],
|
|
30
|
+
["not", "Negates a checked condition; use `not in` for negative membership and `is not` for a negative runtime type test."],
|
|
31
|
+
["in", "Tests List, Set, Map, Record, or string membership; `not in` is its direct negative form."],
|
|
32
|
+
["is", "Tests a value against a runtime type and narrows stable locations; `is not` is its direct negative form."],
|
|
33
|
+
["constructor", "Initializes class fields and calls super(...) first when the class extends another class."],
|
|
34
|
+
["type", "Declares one data shape used for static checking and runtime validation."],
|
|
35
|
+
["enum", "Declares a finite set of string-backed values for application states."],
|
|
36
|
+
["abstract", "Marks a class, instance method, or getter as an incomplete behavior contract."],
|
|
37
|
+
["extends", "Declares one base class; pass base arguments through super(...) in the constructor."],
|
|
38
|
+
["override", "Explicitly replaces a compatible inherited instance method or getter."],
|
|
39
|
+
["private", "Keeps one class field, getter, or method inside its declaring class."],
|
|
40
|
+
["static", "Declares a field, getter, or method on the class rather than an instance."],
|
|
41
|
+
["get", "Declares a typed read-only property computed when it is read."],
|
|
42
|
+
["super", "Calls the base constructor or reads and calls inherited behavior."],
|
|
43
|
+
["def", "Declares a named function with an indentation-based body."],
|
|
44
|
+
["match", "Selects literal, enum, type, object, or List patterns with bindings and guards, without fallthrough."],
|
|
45
|
+
["case", "Declares a match pattern; object and List patterns may destructure values with ...rest and as bindings."],
|
|
46
|
+
["const", "Declares an initialized binding that cannot be rebound."],
|
|
47
|
+
["let", "Declares an initialized binding that can be rebound."],
|
|
48
|
+
["readonly", "Creates a transitive compile-time view over data records and collections without changing runtime identity."],
|
|
49
|
+
["null", "The only empty value in ordinary VelarScript source; undefined is not exposed."],
|
|
50
|
+
]);
|
|
51
|
+
const builtinTypeDocumentation = new Map([
|
|
52
|
+
["string", "A JavaScript string with VelarScript text operations."],
|
|
53
|
+
["number", "A JavaScript number type; number(text) strictly parses complete finite decimal text and returns number?."],
|
|
54
|
+
["bool", "The `true` or `false` boolean type."],
|
|
55
|
+
["unknown", "An unchecked boundary value that must be validated before ordinary use."],
|
|
56
|
+
["List", "An ordered collection with one checked element type."],
|
|
57
|
+
["Map", "An insertion-ordered JavaScript Map with checked key and value types."],
|
|
58
|
+
["Record", "A JSON-safe plain record with dynamic string keys and one checked value type."],
|
|
59
|
+
["Set", "An insertion-ordered JavaScript Set with one checked element type."],
|
|
60
|
+
["Promise", "A JavaScript Promise with one checked resolved-value type."],
|
|
61
|
+
["Duration", "A Core duration value written with an ms or s suffix."],
|
|
62
|
+
]);
|
|
63
|
+
// D57 rules 134/136: both halves of this list are derived. The prelude and
|
|
64
|
+
// namespace entries are keyed by the Core vocabulary roster, so a name added
|
|
65
|
+
// there cannot be missing here — the hand-kept version had already lost `Math`
|
|
66
|
+
// and `number`. The module entries are filtered by the migration state, so a
|
|
67
|
+
// completion cannot offer an import VEL3008 refuses on the next keystroke.
|
|
68
|
+
const corePreludeCompletionDetail = {
|
|
69
|
+
number: "number(text) -> number? — text to number, null when the text is not numeric",
|
|
70
|
+
str: "str(value) -> string",
|
|
71
|
+
print: "print(value) -> null",
|
|
72
|
+
equals: "equals(a, b) -> bool — deep structural comparison over data",
|
|
73
|
+
range: "range(stop) or range(start, stop, step) -> List<number>",
|
|
74
|
+
};
|
|
75
|
+
const permanentNamespaceCompletionDetail = {
|
|
76
|
+
Json: "Permanent namespace for parse, tryParse, stringify, stableStringify, clone, and isSerializable",
|
|
77
|
+
Promise: "Permanent namespace for all, race, sleep, timeout, retry, map, and series",
|
|
78
|
+
Text: "Permanent namespace for Unicode-aware text normalization, formatting, code points, and patterns",
|
|
79
|
+
Math: "Permanent namespace for numeric constants, transforms, transcendentals, and random helpers",
|
|
80
|
+
};
|
|
81
|
+
const standardModuleCompletionDetail = new Map([
|
|
82
|
+
["velar/collections", "Typed collection transforms and Python-style iteration helpers"],
|
|
83
|
+
["velar/math", "Numeric constants, transforms, and random helpers"],
|
|
84
|
+
["velar/async", "Promise composition, timeout, retry, and concurrency helpers"],
|
|
85
|
+
["velar/url", "URL parsing, joining, encoding, and query helpers"],
|
|
86
|
+
["velar/time", "Timestamps, ISO values, formatting, and date-part helpers"],
|
|
87
|
+
["velar/id", "Secure host UUID generation and validation"],
|
|
88
|
+
["velar/log", "Structured leveled logging with scoped loggers and replaceable sinks"],
|
|
89
|
+
["velar/test", "Typed deep, collection, error, and Promise assertions"],
|
|
90
|
+
]);
|
|
91
|
+
function importableStandardModules() {
|
|
92
|
+
const interfaces = standardModuleInterfaces();
|
|
93
|
+
return [...standardModuleCompletionDetail]
|
|
94
|
+
.filter(([source]) => permanentNamespaceCoveringModule(source, interfaces.get(source)?.exports.keys() ?? []) === null)
|
|
95
|
+
.map(([label, detail]) => ({ label, kind: 9, detail }));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* D62 rule 157: the editor's keyword list is the lexer's hard-keyword table
|
|
99
|
+
* plus Core's contextual roster, read rather than retyped. The hand-kept copy
|
|
100
|
+
* this replaced held thirty-nine labels and was a partial copy of both: it had
|
|
101
|
+
* never learned `js`, `unsafe`, `extern`, `module`, `break`, `continue` or
|
|
102
|
+
* `is`, and it knew six of the ten contextual words. Neither omission could
|
|
103
|
+
* have been noticed by anything but a reader counting two lists by hand.
|
|
104
|
+
*/
|
|
105
|
+
const coreCompletionItems = [
|
|
106
|
+
...[...Object.keys(keywordKinds), ...CORE_CONTEXTUAL_KEYWORD_WORDS].map((label) => ({ label, kind: 14 })),
|
|
107
|
+
...[...builtinTypeDocumentation].map(([label, detail]) => ({ label, kind: 7, detail })),
|
|
108
|
+
...CORE_PRELUDE_NAMES.map((label) => ({ label, kind: 3, detail: corePreludeCompletionDetail[label] })),
|
|
109
|
+
...PERMANENT_NAMESPACE_NAMES.map((label) => ({ label, kind: 6, detail: permanentNamespaceCompletionDetail[label] })),
|
|
110
|
+
...importableStandardModules(),
|
|
111
|
+
];
|
|
112
|
+
export function completionItemsFor(project) {
|
|
113
|
+
if (!project)
|
|
114
|
+
return coreCompletionItems;
|
|
115
|
+
return [
|
|
116
|
+
...coreCompletionItems,
|
|
117
|
+
...project.compilerExtensions.flatMap((extension) => [
|
|
118
|
+
...(extension.editor?.completions ?? []),
|
|
119
|
+
...Object.entries(extension.editor?.typeDocumentation ?? {}).map(([label, detail]) => ({ label, kind: 7, detail })),
|
|
120
|
+
]),
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
function extensionDocumentation(project, kind, word) {
|
|
124
|
+
for (const extension of project?.compilerExtensions ?? []) {
|
|
125
|
+
const documentation = extension.editor?.[kind]?.[word];
|
|
126
|
+
if (documentation)
|
|
127
|
+
return documentation;
|
|
128
|
+
}
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
export async function runLanguageServer() {
|
|
132
|
+
activePositionEncoding = "utf-16";
|
|
133
|
+
confinedWorkspaceRoot = configuredWorkspaceRoot();
|
|
134
|
+
const configuredCanonical = configuredCanonicalRoot() ?? confinedWorkspaceRoot;
|
|
135
|
+
confinedCanonicalRoot = configuredCanonical ? await canonicalizePotentialPath(configuredCanonical) : null;
|
|
136
|
+
const documents = new Map();
|
|
137
|
+
const scriptDocuments = new Map();
|
|
138
|
+
const sessions = new VelarProjectSessions();
|
|
139
|
+
const workspaceIndex = new WorkspaceTextIndex();
|
|
140
|
+
const pendingRequests = new Set();
|
|
141
|
+
const cancelledRequests = new Set();
|
|
142
|
+
const diagnosticUris = new Set();
|
|
143
|
+
let buffer = Buffer.alloc(0);
|
|
144
|
+
let queue = Promise.resolve();
|
|
145
|
+
let diagnosticTask = null;
|
|
146
|
+
let workspaceIndexTask = Promise.resolve();
|
|
147
|
+
let workspaceIndexFailure = null;
|
|
148
|
+
let shuttingDown = false;
|
|
149
|
+
let finish = () => { };
|
|
150
|
+
const exitRequested = new Promise((resolve) => { finish = resolve; });
|
|
151
|
+
const send = (message) => {
|
|
152
|
+
const json = JSON.stringify(message);
|
|
153
|
+
if (Buffer.byteLength(json, "utf8") <= MAX_LSP_MESSAGE_BYTES) {
|
|
154
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(json, "utf8")}\r\n\r\n${json}`);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const value = message;
|
|
158
|
+
const fallback = value.id !== undefined
|
|
159
|
+
? { jsonrpc: "2.0", id: value.id ?? null, error: { code: -32603, message: "VelarScript LSP response exceeds the 16 MiB transport limit" } }
|
|
160
|
+
: value.method === "textDocument/publishDiagnostics"
|
|
161
|
+
? oversizedDiagnosticsFallback(value.params)
|
|
162
|
+
: null;
|
|
163
|
+
if (!fallback)
|
|
164
|
+
return;
|
|
165
|
+
const fallbackJson = JSON.stringify(fallback);
|
|
166
|
+
process.stdout.write(`Content-Length: ${Buffer.byteLength(fallbackJson, "utf8")}\r\n\r\n${fallbackJson}`);
|
|
167
|
+
};
|
|
168
|
+
const overrides = () => new Map([...documents.values()].flatMap((item) => {
|
|
169
|
+
if (scriptDocuments.has(item.uri))
|
|
170
|
+
return [];
|
|
171
|
+
const itemPath = pathOf(item.uri);
|
|
172
|
+
return itemPath ? [[itemPath, item.text]] : [];
|
|
173
|
+
}));
|
|
174
|
+
const projectFor = async (document) => {
|
|
175
|
+
if (scriptDocuments.has(document.uri))
|
|
176
|
+
return null;
|
|
177
|
+
const path = pathOf(document.uri);
|
|
178
|
+
if (!path)
|
|
179
|
+
return null;
|
|
180
|
+
return (await sessions.update(path, new Set(), overrides())).project;
|
|
181
|
+
};
|
|
182
|
+
const queueWorkspaceIndex = (operation) => {
|
|
183
|
+
const result = workspaceIndexTask.then(operation);
|
|
184
|
+
workspaceIndexTask = result.then(() => { workspaceIndexFailure = null; }, (error) => { workspaceIndexFailure = hostErrorMessage(error); });
|
|
185
|
+
return result;
|
|
186
|
+
};
|
|
187
|
+
const waitForWorkspaceIndex = async (id) => {
|
|
188
|
+
while (true) {
|
|
189
|
+
if (id !== undefined && cancelledRequests.has(requestKey(id)))
|
|
190
|
+
throw new WorkspaceIndexCancelledError();
|
|
191
|
+
const task = workspaceIndexTask;
|
|
192
|
+
let settled = false;
|
|
193
|
+
await Promise.race([
|
|
194
|
+
task.then(() => { settled = true; }),
|
|
195
|
+
new Promise((resolveWait) => setImmediate(resolveWait)),
|
|
196
|
+
]);
|
|
197
|
+
if (settled && task === workspaceIndexTask)
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
if (workspaceIndexFailure)
|
|
201
|
+
throw new Error(`Workspace index unavailable: ${workspaceIndexFailure}`);
|
|
202
|
+
};
|
|
203
|
+
const publish = async (document) => {
|
|
204
|
+
const current = documents.get(document.uri);
|
|
205
|
+
if (!current || current.version !== document.version)
|
|
206
|
+
return;
|
|
207
|
+
const script = scriptDocuments.get(document.uri);
|
|
208
|
+
if (script) {
|
|
209
|
+
send({
|
|
210
|
+
jsonrpc: "2.0",
|
|
211
|
+
method: "textDocument/publishDiagnostics",
|
|
212
|
+
params: {
|
|
213
|
+
uri: document.uri,
|
|
214
|
+
version: document.version,
|
|
215
|
+
diagnostics: boundedScriptDiagnostics(document.text, script.analysis()),
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
let diagnostics = [];
|
|
221
|
+
let notices = [];
|
|
222
|
+
let source;
|
|
223
|
+
try {
|
|
224
|
+
const path = pathOf(document.uri);
|
|
225
|
+
if (path) {
|
|
226
|
+
const project = await projectFor(document);
|
|
227
|
+
const module = project?.modules.find((item) => item.inputPath === path);
|
|
228
|
+
if (module) {
|
|
229
|
+
diagnostics = [
|
|
230
|
+
...module.result.diagnostics,
|
|
231
|
+
...(project?.failures ?? [])
|
|
232
|
+
.filter((failure) => failure.path === path)
|
|
233
|
+
.map((failure) => ({ code: "VEL9001", message: failure.message, span: { start: 0, end: 1 } })),
|
|
234
|
+
];
|
|
235
|
+
notices = (project?.notices ?? []).filter((notice) => notice.path === path).map((notice) => notice.message);
|
|
236
|
+
source = module.result.source;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
const result = compile(document.text, { path });
|
|
240
|
+
diagnostics = result.diagnostics;
|
|
241
|
+
source = result.source;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
const result = compile(document.text, { path: document.uri });
|
|
246
|
+
diagnostics = result.diagnostics;
|
|
247
|
+
source = result.source;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const result = compile(document.text, { path: document.uri });
|
|
252
|
+
diagnostics = [{ code: "VEL9001", message: hostErrorMessage(error), span: { start: 0, end: 1 } }];
|
|
253
|
+
source = result.source;
|
|
254
|
+
}
|
|
255
|
+
if (documents.get(document.uri)?.version !== document.version)
|
|
256
|
+
return;
|
|
257
|
+
send({
|
|
258
|
+
jsonrpc: "2.0",
|
|
259
|
+
method: "textDocument/publishDiagnostics",
|
|
260
|
+
params: {
|
|
261
|
+
uri: document.uri,
|
|
262
|
+
version: document.version,
|
|
263
|
+
diagnostics: boundedDiagnostics(source, diagnostics, notices),
|
|
264
|
+
},
|
|
265
|
+
});
|
|
266
|
+
};
|
|
267
|
+
const schedulePublish = (uri) => {
|
|
268
|
+
diagnosticUris.add(uri);
|
|
269
|
+
if (diagnosticTask)
|
|
270
|
+
return;
|
|
271
|
+
diagnosticTask = new Promise((resolve) => setImmediate(resolve))
|
|
272
|
+
.then(async () => {
|
|
273
|
+
while (diagnosticUris.size > 0) {
|
|
274
|
+
const uris = [...diagnosticUris];
|
|
275
|
+
diagnosticUris.clear();
|
|
276
|
+
for (const pendingUri of uris) {
|
|
277
|
+
const document = documents.get(pendingUri);
|
|
278
|
+
if (document)
|
|
279
|
+
await publish(document);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
})
|
|
283
|
+
.finally(() => {
|
|
284
|
+
diagnosticTask = null;
|
|
285
|
+
if (diagnosticUris.size > 0)
|
|
286
|
+
schedulePublish([...diagnosticUris][0]);
|
|
287
|
+
});
|
|
288
|
+
};
|
|
289
|
+
const refreshWorkspaceProjects = async (changedPaths) => {
|
|
290
|
+
const currentOverrides = overrides();
|
|
291
|
+
const roots = new Map();
|
|
292
|
+
for (const document of documents.values()) {
|
|
293
|
+
if (scriptDocuments.has(document.uri))
|
|
294
|
+
continue;
|
|
295
|
+
const documentPath = pathOf(document.uri);
|
|
296
|
+
if (!documentPath)
|
|
297
|
+
continue;
|
|
298
|
+
roots.set(sessions.rootFor(documentPath) ?? documentPath, documentPath);
|
|
299
|
+
}
|
|
300
|
+
for (const documentPath of roots.values()) {
|
|
301
|
+
if (changedPaths)
|
|
302
|
+
await sessions.update(documentPath, changedPaths, currentOverrides);
|
|
303
|
+
else
|
|
304
|
+
await sessions.snapshot(documentPath, currentOverrides);
|
|
305
|
+
}
|
|
306
|
+
for (const document of documents.values())
|
|
307
|
+
schedulePublish(document.uri);
|
|
308
|
+
};
|
|
309
|
+
const finishRequest = (id) => {
|
|
310
|
+
if (id === undefined)
|
|
311
|
+
return false;
|
|
312
|
+
const key = requestKey(id);
|
|
313
|
+
pendingRequests.delete(key);
|
|
314
|
+
return cancelledRequests.delete(key);
|
|
315
|
+
};
|
|
316
|
+
const respond = (id, result) => {
|
|
317
|
+
if (finishRequest(id)) {
|
|
318
|
+
send({ jsonrpc: "2.0", id: id ?? null, error: { code: -32800, message: "Request cancelled" } });
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
send({ jsonrpc: "2.0", id: id ?? null, result });
|
|
322
|
+
};
|
|
323
|
+
const respondError = (id, message, code = -32803) => {
|
|
324
|
+
if (finishRequest(id)) {
|
|
325
|
+
send({ jsonrpc: "2.0", id: id ?? null, error: { code: -32800, message: "Request cancelled" } });
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
send({ jsonrpc: "2.0", id: id ?? null, error: { code, message } });
|
|
329
|
+
};
|
|
330
|
+
const handle = async (message) => {
|
|
331
|
+
if (message.id !== undefined && cancelledRequests.has(requestKey(message.id))) {
|
|
332
|
+
respondError(message.id, "Request cancelled", -32800);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
if (shuttingDown && message.method !== "exit") {
|
|
336
|
+
if (message.id !== undefined)
|
|
337
|
+
respondError(message.id, "VelarScript Language Server is shutting down", -32600);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const params = message.params;
|
|
341
|
+
switch (message.method) {
|
|
342
|
+
case "initialize":
|
|
343
|
+
activePositionEncoding = requestedPositionEncoding(params);
|
|
344
|
+
try {
|
|
345
|
+
workspaceIndex.configure(requestedWorkspaceRoots(params));
|
|
346
|
+
}
|
|
347
|
+
catch (error) {
|
|
348
|
+
respondError(message.id, hostErrorMessage(error), -32602);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
respond(message.id, {
|
|
352
|
+
capabilities: {
|
|
353
|
+
positionEncoding: activePositionEncoding,
|
|
354
|
+
textDocumentSync: { openClose: true, change: 2, save: { includeText: true } },
|
|
355
|
+
completionProvider: { triggerCharacters: [".", "<", " ", "{", ",", ":"] },
|
|
356
|
+
hoverProvider: true,
|
|
357
|
+
documentFormattingProvider: true,
|
|
358
|
+
definitionProvider: true,
|
|
359
|
+
referencesProvider: true,
|
|
360
|
+
documentHighlightProvider: true,
|
|
361
|
+
renameProvider: { prepareProvider: true },
|
|
362
|
+
documentSymbolProvider: true,
|
|
363
|
+
workspaceSymbolProvider: true,
|
|
364
|
+
signatureHelpProvider: { triggerCharacters: ["(", ","] },
|
|
365
|
+
inlayHintProvider: true,
|
|
366
|
+
semanticTokensProvider: {
|
|
367
|
+
legend: { tokenTypes: semanticTokenTypes, tokenModifiers: semanticTokenModifiers },
|
|
368
|
+
full: true,
|
|
369
|
+
},
|
|
370
|
+
codeActionProvider: { codeActionKinds: ["quickfix"] },
|
|
371
|
+
experimental: {
|
|
372
|
+
velar: {
|
|
373
|
+
protocolVersion: VELAR_LANGUAGE_SERVER_PROTOCOL_VERSION,
|
|
374
|
+
incrementalSessions: true,
|
|
375
|
+
watchedFiles: true,
|
|
376
|
+
workspaceRescan: true,
|
|
377
|
+
cancellation: true,
|
|
378
|
+
scriptLanguages: ["javascript", "typescript"],
|
|
379
|
+
scriptImplementation: "velarscript",
|
|
380
|
+
incrementalScriptLexing: true,
|
|
381
|
+
workspaceSearch: true,
|
|
382
|
+
workspaceTextExtensions: WORKSPACE_TEXT_EXTENSIONS,
|
|
383
|
+
workspaceTextFileLimit: MAX_WORKSPACE_TEXT_FILES,
|
|
384
|
+
workspaceSearchResultLimit: MAX_WORKSPACE_SEARCH_RESULTS,
|
|
385
|
+
workspaceWatchPathLimit: MAX_WORKSPACE_CHANGE_PATHS,
|
|
386
|
+
workspaceWatchPathCodeUnitLimit: MAX_WORKSPACE_CHANGE_PATH_CODE_UNITS,
|
|
387
|
+
workspaceWatchTextCodeUnitLimit: MAX_WORKSPACE_CHANGE_TEXT_CODE_UNITS,
|
|
388
|
+
ownershipGraph: true,
|
|
389
|
+
ownershipGraphNodeLimit: MAX_OWNERSHIP_GRAPH_NODES,
|
|
390
|
+
ownershipGraphEdgeLimit: MAX_OWNERSHIP_GRAPH_EDGES,
|
|
391
|
+
emittedJavaScript: true,
|
|
392
|
+
emittedJavaScriptCodeUnitLimit: MAX_EMITTED_JAVASCRIPT_CHARS,
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
serverInfo: { name: "VelarScript Language Server", version: VELAR_VERSION },
|
|
397
|
+
});
|
|
398
|
+
break;
|
|
399
|
+
case "initialized":
|
|
400
|
+
void queueWorkspaceIndex(() => workspaceIndex.rescan()).catch(() => {
|
|
401
|
+
// The next explicit search reports the retained indexing failure through its request response.
|
|
402
|
+
});
|
|
403
|
+
break;
|
|
404
|
+
case "shutdown":
|
|
405
|
+
shuttingDown = true;
|
|
406
|
+
respond(message.id, null);
|
|
407
|
+
break;
|
|
408
|
+
case "exit":
|
|
409
|
+
process.exitCode = shuttingDown ? 0 : 1;
|
|
410
|
+
process.stdin.pause();
|
|
411
|
+
finish();
|
|
412
|
+
break;
|
|
413
|
+
case "$/cancelRequest":
|
|
414
|
+
break;
|
|
415
|
+
case "textDocument/didOpen": {
|
|
416
|
+
const value = params?.textDocument;
|
|
417
|
+
const path = await authorizedPathOf(value.uri);
|
|
418
|
+
if ((confinedWorkspaceRoot || confinedCanonicalRoot) && !path) {
|
|
419
|
+
send({ jsonrpc: "2.0", method: "window/logMessage", params: { type: 2, message: "Ignored a document outside the Desktop project grant" } });
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
documents.set(value.uri, value);
|
|
423
|
+
if (path)
|
|
424
|
+
workspaceIndex.openDocument(path, value.text);
|
|
425
|
+
const scriptLanguage = scriptLanguageFor(path ?? value.uri, value.languageId);
|
|
426
|
+
const script = scriptLanguage ? createScriptLanguageDocument(scriptLanguage, value.text) : null;
|
|
427
|
+
if (script)
|
|
428
|
+
scriptDocuments.set(value.uri, script);
|
|
429
|
+
if (path && !script) {
|
|
430
|
+
try {
|
|
431
|
+
await sessions.snapshot(path, overrides());
|
|
432
|
+
}
|
|
433
|
+
catch { /* publish converts project/config failures into document diagnostics. */ }
|
|
434
|
+
}
|
|
435
|
+
await publish(value);
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
case "textDocument/didChange": {
|
|
439
|
+
const descriptor = params?.textDocument;
|
|
440
|
+
const current = documents.get(descriptor.uri);
|
|
441
|
+
if (!current || !Number.isSafeInteger(descriptor.version) || descriptor.version <= current.version)
|
|
442
|
+
break;
|
|
443
|
+
const changes = params?.contentChanges;
|
|
444
|
+
if (!Array.isArray(changes))
|
|
445
|
+
break;
|
|
446
|
+
const next = { ...current, version: descriptor.version, text: applyContentChanges(current.text, changes) };
|
|
447
|
+
documents.set(next.uri, next);
|
|
448
|
+
const nextPath = pathOf(next.uri);
|
|
449
|
+
if (nextPath)
|
|
450
|
+
workspaceIndex.changeDocument(nextPath, next.text);
|
|
451
|
+
const script = scriptDocuments.get(next.uri);
|
|
452
|
+
const edit = script ? scriptTextEdit(current.text, next.text) : null;
|
|
453
|
+
if (script && edit)
|
|
454
|
+
script.apply([edit]);
|
|
455
|
+
schedulePublish(next.uri);
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
case "textDocument/didSave": {
|
|
459
|
+
const descriptor = params?.textDocument;
|
|
460
|
+
const current = documents.get(descriptor.uri);
|
|
461
|
+
if (current)
|
|
462
|
+
schedulePublish(current.uri);
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
case "textDocument/didClose": {
|
|
466
|
+
const descriptor = params?.textDocument;
|
|
467
|
+
documents.delete(descriptor.uri);
|
|
468
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
469
|
+
scriptDocuments.delete(descriptor.uri);
|
|
470
|
+
diagnosticUris.delete(descriptor.uri);
|
|
471
|
+
const path = pathOf(descriptor.uri);
|
|
472
|
+
if (path)
|
|
473
|
+
await queueWorkspaceIndex(() => workspaceIndex.closeDocument(path));
|
|
474
|
+
if (path && !script)
|
|
475
|
+
await sessions.update(path, new Set([path]), overrides());
|
|
476
|
+
send({ jsonrpc: "2.0", method: "textDocument/publishDiagnostics", params: { uri: descriptor.uri, diagnostics: [] } });
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "workspace/didChangeWatchedFiles": {
|
|
480
|
+
const changes = params?.changes;
|
|
481
|
+
if (!Array.isArray(changes))
|
|
482
|
+
break;
|
|
483
|
+
let fidelityLost = changes.length > MAX_WORKSPACE_CHANGE_PATHS;
|
|
484
|
+
let pathUnits = 0;
|
|
485
|
+
const urisByPath = new Map();
|
|
486
|
+
if (!fidelityLost) {
|
|
487
|
+
for (const change of changes) {
|
|
488
|
+
const uri = change?.uri;
|
|
489
|
+
if (typeof uri !== "string" || uri.length > MAX_WORKSPACE_CHANGE_PATH_CODE_UNITS * 3 + 32) {
|
|
490
|
+
fidelityLost = true;
|
|
491
|
+
break;
|
|
492
|
+
}
|
|
493
|
+
const rawPath = rawPathOf(uri);
|
|
494
|
+
if (!rawPath || rawPath.length > MAX_WORKSPACE_CHANGE_PATH_CODE_UNITS) {
|
|
495
|
+
fidelityLost = true;
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
if (!urisByPath.has(rawPath)) {
|
|
499
|
+
pathUnits += rawPath.length;
|
|
500
|
+
if (pathUnits > MAX_WORKSPACE_CHANGE_TEXT_CODE_UNITS) {
|
|
501
|
+
fidelityLost = true;
|
|
502
|
+
break;
|
|
503
|
+
}
|
|
504
|
+
urisByPath.set(rawPath, uri);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
if (fidelityLost) {
|
|
509
|
+
send({ jsonrpc: "2.0", method: "window/logMessage", params: { type: 2, message: "Watcher batch exceeded the official bounds; rebuilt the initialized workspace index" } });
|
|
510
|
+
await queueWorkspaceIndex(() => workspaceIndex.rescan());
|
|
511
|
+
await refreshWorkspaceProjects(null);
|
|
512
|
+
break;
|
|
513
|
+
}
|
|
514
|
+
const changedPaths = new Set();
|
|
515
|
+
const uris = [...urisByPath.values()];
|
|
516
|
+
const authorizedPaths = await mapBounded(uris, 16, authorizedPathOf);
|
|
517
|
+
for (let index = 0; index < uris.length; index += 1) {
|
|
518
|
+
const path = authorizedPaths[index];
|
|
519
|
+
if (path)
|
|
520
|
+
changedPaths.add(path);
|
|
521
|
+
else if (confinedWorkspaceRoot || confinedCanonicalRoot) {
|
|
522
|
+
send({ jsonrpc: "2.0", method: "window/logMessage", params: { type: 2, message: "Ignored a watcher path outside the Desktop project grant" } });
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
if (changedPaths.size === 0)
|
|
526
|
+
break;
|
|
527
|
+
await queueWorkspaceIndex(() => workspaceIndex.update(changedPaths));
|
|
528
|
+
await refreshWorkspaceProjects(changedPaths);
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
case "velar/workspaceRescan": {
|
|
532
|
+
const activity = await queueWorkspaceIndex(() => workspaceIndex.rescan(() => message.id !== undefined && cancelledRequests.has(requestKey(message.id))));
|
|
533
|
+
await refreshWorkspaceProjects(null);
|
|
534
|
+
if (message.id !== undefined)
|
|
535
|
+
respond(message.id, activity);
|
|
536
|
+
break;
|
|
537
|
+
}
|
|
538
|
+
case "velar/workspaceSearch": {
|
|
539
|
+
const query = params?.query;
|
|
540
|
+
const caseSensitive = params?.caseSensitive;
|
|
541
|
+
const maximumResults = params?.maximumResults;
|
|
542
|
+
if (typeof query !== "string"
|
|
543
|
+
|| (caseSensitive !== undefined && typeof caseSensitive !== "boolean")
|
|
544
|
+
|| (maximumResults !== undefined && typeof maximumResults !== "number")) {
|
|
545
|
+
respondError(message.id, "velar/workspaceSearch requires a string query and optional boolean caseSensitive and numeric maximumResults", -32602);
|
|
546
|
+
break;
|
|
547
|
+
}
|
|
548
|
+
if (query.length === 0 || query.length > 1_024
|
|
549
|
+
|| (maximumResults !== undefined && (!Number.isSafeInteger(maximumResults)
|
|
550
|
+
|| maximumResults < 1 || maximumResults > MAX_WORKSPACE_SEARCH_RESULTS))) {
|
|
551
|
+
respondError(message.id, `velar/workspaceSearch query must contain 1 through 1024 UTF-16 code units and maximumResults must be an integer from 1 through ${MAX_WORKSPACE_SEARCH_RESULTS}`, -32602);
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
await waitForWorkspaceIndex(message.id);
|
|
555
|
+
const result = await workspaceIndex.search(query, {
|
|
556
|
+
...(caseSensitive === undefined ? {} : { caseSensitive }),
|
|
557
|
+
...(maximumResults === undefined ? {} : { maximumResults }),
|
|
558
|
+
cancelled: () => message.id !== undefined && cancelledRequests.has(requestKey(message.id)),
|
|
559
|
+
});
|
|
560
|
+
respond(message.id, {
|
|
561
|
+
items: result.matches.map((match) => ({
|
|
562
|
+
uri: pathToFileURL(match.path).href,
|
|
563
|
+
range: {
|
|
564
|
+
start: workspacePosition(match.start),
|
|
565
|
+
end: workspacePosition(match.end),
|
|
566
|
+
},
|
|
567
|
+
preview: match.preview,
|
|
568
|
+
})),
|
|
569
|
+
limitReached: result.limitReached,
|
|
570
|
+
filesSearched: result.filesSearched,
|
|
571
|
+
indexedFiles: result.indexedFiles,
|
|
572
|
+
indexedBytes: result.indexedBytes,
|
|
573
|
+
revision: result.revision,
|
|
574
|
+
durationMs: result.durationMs,
|
|
575
|
+
coverageComplete: result.coverageComplete,
|
|
576
|
+
});
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case "velar/ownershipGraph": {
|
|
580
|
+
const descriptor = params?.textDocument;
|
|
581
|
+
const requestedVersionValue = params?.version;
|
|
582
|
+
const maximumNodesValue = params?.maximumNodes;
|
|
583
|
+
const maximumEdgesValue = params?.maximumEdges;
|
|
584
|
+
if (!descriptor || typeof descriptor.uri !== "string"
|
|
585
|
+
|| (requestedVersionValue !== undefined && (typeof requestedVersionValue !== "number" || !Number.isSafeInteger(requestedVersionValue)))
|
|
586
|
+
|| (maximumNodesValue !== undefined && (typeof maximumNodesValue !== "number" || !Number.isSafeInteger(maximumNodesValue)))
|
|
587
|
+
|| (maximumEdgesValue !== undefined && (typeof maximumEdgesValue !== "number" || !Number.isSafeInteger(maximumEdgesValue)))) {
|
|
588
|
+
respondError(message.id, "velar/ownershipGraph requires textDocument.uri and optional integer version, maximumNodes, and maximumEdges", -32602);
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
const requestedVersion = requestedVersionValue;
|
|
592
|
+
const maximumNodes = maximumNodesValue;
|
|
593
|
+
const maximumEdges = maximumEdgesValue;
|
|
594
|
+
if ((maximumNodes !== undefined && (maximumNodes < 1 || maximumNodes > MAX_OWNERSHIP_GRAPH_NODES))
|
|
595
|
+
|| (maximumEdges !== undefined && (maximumEdges < 1 || maximumEdges > MAX_OWNERSHIP_GRAPH_EDGES))) {
|
|
596
|
+
respondError(message.id, `velar/ownershipGraph bounds are 1..${MAX_OWNERSHIP_GRAPH_NODES} nodes and 1..${MAX_OWNERSHIP_GRAPH_EDGES} edges`, -32602);
|
|
597
|
+
break;
|
|
598
|
+
}
|
|
599
|
+
const document = documents.get(descriptor.uri);
|
|
600
|
+
if (!document || scriptDocuments.has(descriptor.uri)) {
|
|
601
|
+
respondError(message.id, "velar/ownershipGraph requires an open VelarScript document", -32602);
|
|
602
|
+
break;
|
|
603
|
+
}
|
|
604
|
+
if (requestedVersion !== undefined && requestedVersion !== document.version) {
|
|
605
|
+
respondError(message.id, `Document version ${requestedVersion} is no longer current`, -32801);
|
|
606
|
+
break;
|
|
607
|
+
}
|
|
608
|
+
const project = await projectFor(document);
|
|
609
|
+
if (!project) {
|
|
610
|
+
respondError(message.id, "VelarScript project is unavailable for this document");
|
|
611
|
+
break;
|
|
612
|
+
}
|
|
613
|
+
const graph = await buildOwnershipGraph(project, {
|
|
614
|
+
...(maximumNodes === undefined ? {} : { maximumNodes }),
|
|
615
|
+
...(maximumEdges === undefined ? {} : { maximumEdges }),
|
|
616
|
+
cancelled: () => message.id !== undefined && cancelledRequests.has(requestKey(message.id)),
|
|
617
|
+
});
|
|
618
|
+
respond(message.id, {
|
|
619
|
+
protocolVersion: 1,
|
|
620
|
+
rootUri: pathToFileURL(project.projectRoot).href,
|
|
621
|
+
document: { uri: descriptor.uri, version: document.version },
|
|
622
|
+
compilerVersion: VELAR_VERSION,
|
|
623
|
+
revision: graph.revision,
|
|
624
|
+
nodes: graph.nodes.map((node) => ({
|
|
625
|
+
id: node.id,
|
|
626
|
+
kind: node.kind,
|
|
627
|
+
name: clipLspText(node.name),
|
|
628
|
+
...(node.type ? { type: clipLspText(node.type) } : {}),
|
|
629
|
+
...(node.exported === undefined ? {} : { exported: node.exported }),
|
|
630
|
+
...(node.mutable === undefined ? {} : { mutable: node.mutable }),
|
|
631
|
+
...(node.path && node.span ? { uri: pathToFileURL(node.path).href, range: lspRange(sourceFor(project, node.path), node.span) } : {}),
|
|
632
|
+
...(node.path && node.selectionSpan ? { selectionRange: lspRange(sourceFor(project, node.path), node.selectionSpan) } : {}),
|
|
633
|
+
})),
|
|
634
|
+
edges: graph.edges.map((edge) => ({
|
|
635
|
+
id: edge.id,
|
|
636
|
+
kind: edge.kind,
|
|
637
|
+
from: edge.from,
|
|
638
|
+
to: edge.to,
|
|
639
|
+
...(edge.path && edge.span ? { uri: pathToFileURL(edge.path).href, range: lspRange(sourceFor(project, edge.path), edge.span) } : {}),
|
|
640
|
+
})),
|
|
641
|
+
coverage: graph.coverage,
|
|
642
|
+
limitReached: graph.limitReached,
|
|
643
|
+
durationMs: graph.durationMs,
|
|
644
|
+
});
|
|
645
|
+
break;
|
|
646
|
+
}
|
|
647
|
+
case "velar/emittedJavaScript": {
|
|
648
|
+
const descriptor = params?.textDocument;
|
|
649
|
+
const requestedVersionValue = params?.version;
|
|
650
|
+
const maximumCharsValue = params?.maximumChars;
|
|
651
|
+
if (!descriptor || typeof descriptor.uri !== "string"
|
|
652
|
+
|| (requestedVersionValue !== undefined && (typeof requestedVersionValue !== "number" || !Number.isSafeInteger(requestedVersionValue)))
|
|
653
|
+
|| (maximumCharsValue !== undefined && (typeof maximumCharsValue !== "number" || !Number.isSafeInteger(maximumCharsValue)))) {
|
|
654
|
+
respondError(message.id, "velar/emittedJavaScript requires textDocument.uri and optional integer version and maximumChars", -32602);
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
657
|
+
const requestedVersion = requestedVersionValue;
|
|
658
|
+
const maximumChars = maximumCharsValue;
|
|
659
|
+
if (maximumChars !== undefined && (maximumChars < 1 || maximumChars > MAX_EMITTED_JAVASCRIPT_CHARS)) {
|
|
660
|
+
respondError(message.id, `velar/emittedJavaScript maximumChars must be 1 through ${MAX_EMITTED_JAVASCRIPT_CHARS}`, -32602);
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
const document = documents.get(descriptor.uri);
|
|
664
|
+
if (!document || scriptDocuments.has(descriptor.uri)) {
|
|
665
|
+
respondError(message.id, "velar/emittedJavaScript requires an open VelarScript document", -32602);
|
|
666
|
+
break;
|
|
667
|
+
}
|
|
668
|
+
if (requestedVersion !== undefined && requestedVersion !== document.version) {
|
|
669
|
+
respondError(message.id, `Document version ${requestedVersion} is no longer current`, -32801);
|
|
670
|
+
break;
|
|
671
|
+
}
|
|
672
|
+
const path = pathOf(descriptor.uri);
|
|
673
|
+
const project = path ? await projectFor(document) : null;
|
|
674
|
+
const module = path && project ? project.modules.find((item) => item.inputPath === path) : null;
|
|
675
|
+
if (!project || !module) {
|
|
676
|
+
respondError(message.id, "VelarScript project module is unavailable for this document");
|
|
677
|
+
break;
|
|
678
|
+
}
|
|
679
|
+
const limit = maximumChars ?? MAX_EMITTED_JAVASCRIPT_CHARS;
|
|
680
|
+
const code = module.result.code;
|
|
681
|
+
const sourceMap = module.result.sourceMap;
|
|
682
|
+
respond(message.id, {
|
|
683
|
+
protocolVersion: 1,
|
|
684
|
+
uri: descriptor.uri,
|
|
685
|
+
version: document.version,
|
|
686
|
+
compilerVersion: VELAR_VERSION,
|
|
687
|
+
revision: ownershipGraphRevision(project),
|
|
688
|
+
javascript: code === null ? null : code.slice(0, limit),
|
|
689
|
+
sourceMap: sourceMap === null ? null : sourceMap.slice(0, limit),
|
|
690
|
+
generatedChars: code?.length ?? 0,
|
|
691
|
+
sourceMapChars: sourceMap?.length ?? 0,
|
|
692
|
+
limitReached: (code?.length ?? 0) > limit || (sourceMap?.length ?? 0) > limit,
|
|
693
|
+
diagnostics: boundedDiagnostics(module.result.source, module.result.diagnostics, []),
|
|
694
|
+
});
|
|
695
|
+
break;
|
|
696
|
+
}
|
|
697
|
+
case "workspace/symbol": {
|
|
698
|
+
const query = params?.query;
|
|
699
|
+
if (typeof query !== "string") {
|
|
700
|
+
respondError(message.id, "workspace/symbol requires a string query", -32602);
|
|
701
|
+
break;
|
|
702
|
+
}
|
|
703
|
+
if (query.length > 1_024) {
|
|
704
|
+
respondError(message.id, "workspace/symbol query cannot exceed 1024 UTF-16 code units", -32602);
|
|
705
|
+
break;
|
|
706
|
+
}
|
|
707
|
+
await waitForWorkspaceIndex(message.id);
|
|
708
|
+
const projects = new Map();
|
|
709
|
+
for (const document of documents.values()) {
|
|
710
|
+
if (scriptDocuments.has(document.uri))
|
|
711
|
+
continue;
|
|
712
|
+
const path = pathOf(document.uri);
|
|
713
|
+
if (!path)
|
|
714
|
+
continue;
|
|
715
|
+
try {
|
|
716
|
+
const snapshot = await sessions.update(path, new Set(), overrides());
|
|
717
|
+
projects.set(snapshot.config.root, snapshot.project);
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
// Invalid projects already own document diagnostics and cannot poison symbols from healthy workspace roots.
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
let indexedVelarPaths = 0;
|
|
724
|
+
for (const path of workspaceIndex.paths(".vel")) {
|
|
725
|
+
if (message.id !== undefined && cancelledRequests.has(requestKey(message.id)))
|
|
726
|
+
throw new WorkspaceIndexCancelledError();
|
|
727
|
+
indexedVelarPaths += 1;
|
|
728
|
+
if (indexedVelarPaths % 128 === 0)
|
|
729
|
+
await new Promise((resolveYield) => setImmediate(resolveYield));
|
|
730
|
+
try {
|
|
731
|
+
const snapshot = await sessions.update(path, new Set(), overrides());
|
|
732
|
+
projects.set(snapshot.config.root, snapshot.project);
|
|
733
|
+
if (projects.size > 64)
|
|
734
|
+
throw new RangeError("workspace/symbol cannot span more than 64 VelarScript project roots");
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
if (error instanceof RangeError && /more than 64 VelarScript project roots/u.test(error.message))
|
|
738
|
+
throw error;
|
|
739
|
+
// Broken project roots retain their document diagnostics and do not suppress symbols from healthy roots.
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
const symbols = [];
|
|
743
|
+
for (const project of projects.values()) {
|
|
744
|
+
const remaining = MAX_LSP_RESULT_ITEMS - symbols.length;
|
|
745
|
+
if (remaining === 0)
|
|
746
|
+
break;
|
|
747
|
+
symbols.push(...projectWorkspaceSymbols(project, query, remaining).map((symbol) => ({
|
|
748
|
+
name: clipLspText(symbol.name),
|
|
749
|
+
kind: lspSymbolKind(symbol.presentationKind ?? symbol.kind),
|
|
750
|
+
location: lspLocation(project, symbol.path, symbol.selectionSpan),
|
|
751
|
+
...(symbol.containerName ? { containerName: clipLspText(symbol.containerName) } : {}),
|
|
752
|
+
})));
|
|
753
|
+
}
|
|
754
|
+
respond(message.id, symbols);
|
|
755
|
+
break;
|
|
756
|
+
}
|
|
757
|
+
case "textDocument/completion": {
|
|
758
|
+
const descriptor = params?.textDocument;
|
|
759
|
+
const position = params?.position;
|
|
760
|
+
const document = documents.get(descriptor.uri);
|
|
761
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
762
|
+
if (document && script) {
|
|
763
|
+
const items = script.completionsAt(scriptOffsetAt(document.text, position)).slice(0, MAX_LSP_RESULT_ITEMS).map((item) => ({
|
|
764
|
+
label: clipLspText(item.label),
|
|
765
|
+
kind: lspCompletionKind(item.kind),
|
|
766
|
+
detail: clipLspText(item.detail),
|
|
767
|
+
}));
|
|
768
|
+
respond(message.id, { isIncomplete: false, items });
|
|
769
|
+
break;
|
|
770
|
+
}
|
|
771
|
+
const path = pathOf(descriptor.uri);
|
|
772
|
+
const project = document ? await projectFor(document) : null;
|
|
773
|
+
const offset = document ? offsetAt(document.text, position) : 0;
|
|
774
|
+
const semantic = document && path && project ? projectCompletionsAt(project, path, offset) : [];
|
|
775
|
+
const semanticItems = semantic.slice(0, MAX_LSP_RESULT_ITEMS).map((item) => ({
|
|
776
|
+
label: clipLspText(item.label),
|
|
777
|
+
kind: lspCompletionKind(item.presentationKind ?? item.kind),
|
|
778
|
+
detail: clipLspText(item.detail),
|
|
779
|
+
...(item.documentation ? { documentation: { kind: "markdown", value: clipLspText(item.documentation) } } : {}),
|
|
780
|
+
}));
|
|
781
|
+
const completionContext = document && path && project ? projectCompletionContextAt(project, path, offset) : "ordinary";
|
|
782
|
+
const semanticLabels = new Set(semanticItems.map((item) => item.label));
|
|
783
|
+
const items = completionContext !== "ordinary"
|
|
784
|
+
? semanticItems
|
|
785
|
+
: [...semanticItems, ...completionItemsFor(project).filter((item) => !semanticLabels.has(item.label))].slice(0, MAX_LSP_RESULT_ITEMS);
|
|
786
|
+
respond(message.id, { isIncomplete: false, items });
|
|
787
|
+
break;
|
|
788
|
+
}
|
|
789
|
+
case "textDocument/hover": {
|
|
790
|
+
const descriptor = params?.textDocument;
|
|
791
|
+
const position = params?.position;
|
|
792
|
+
const document = documents.get(descriptor.uri);
|
|
793
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
794
|
+
if (document && script) {
|
|
795
|
+
const result = script.hoverAt(scriptOffsetAt(document.text, position));
|
|
796
|
+
respond(message.id, result ? {
|
|
797
|
+
contents: { kind: "markdown", value: `\`\`${clipLspText(result.contents)}\`\`` },
|
|
798
|
+
range: scriptRange(document.text, result),
|
|
799
|
+
} : null);
|
|
800
|
+
break;
|
|
801
|
+
}
|
|
802
|
+
respond(message.id, document ? await hover(document, position, await projectFor(document)) : null);
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "textDocument/definition": {
|
|
806
|
+
const descriptor = params?.textDocument;
|
|
807
|
+
const position = params?.position;
|
|
808
|
+
const document = documents.get(descriptor.uri);
|
|
809
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
810
|
+
if (document && script) {
|
|
811
|
+
const location = script.definitionAt(scriptOffsetAt(document.text, position));
|
|
812
|
+
respond(message.id, location ? { uri: descriptor.uri, range: scriptRange(document.text, location) } : null);
|
|
813
|
+
break;
|
|
814
|
+
}
|
|
815
|
+
const path = pathOf(descriptor.uri);
|
|
816
|
+
const project = document ? await projectFor(document) : null;
|
|
817
|
+
const location = document && path && project ? projectDefinitionAt(project, path, offsetAt(document.text, position)) : null;
|
|
818
|
+
respond(message.id, location && project ? lspLocation(project, location.path, location.span) : null);
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
case "textDocument/references": {
|
|
822
|
+
const descriptor = params?.textDocument;
|
|
823
|
+
const position = params?.position;
|
|
824
|
+
const context = params?.context;
|
|
825
|
+
const document = documents.get(descriptor.uri);
|
|
826
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
827
|
+
if (document && script) {
|
|
828
|
+
const locations = script.referencesAt(scriptOffsetAt(document.text, position), context?.includeDeclaration ?? false)
|
|
829
|
+
.slice(0, MAX_LSP_RESULT_ITEMS)
|
|
830
|
+
.map((span) => ({ uri: descriptor.uri, range: scriptRange(document.text, span) }));
|
|
831
|
+
respond(message.id, locations);
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
const path = pathOf(descriptor.uri);
|
|
835
|
+
const project = document ? await projectFor(document) : null;
|
|
836
|
+
const locations = document && path && project
|
|
837
|
+
? projectReferencesAt(project, path, offsetAt(document.text, position), context?.includeDeclaration ?? false)
|
|
838
|
+
.slice(0, MAX_LSP_RESULT_ITEMS)
|
|
839
|
+
: [];
|
|
840
|
+
respond(message.id, project ? locations.map((location) => lspLocation(project, location.path, location.span)) : []);
|
|
841
|
+
break;
|
|
842
|
+
}
|
|
843
|
+
case "textDocument/documentHighlight": {
|
|
844
|
+
const descriptor = params?.textDocument;
|
|
845
|
+
const position = params?.position;
|
|
846
|
+
const document = documents.get(descriptor.uri);
|
|
847
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
848
|
+
if (document && script) {
|
|
849
|
+
const locations = script.referencesAt(scriptOffsetAt(document.text, position), true)
|
|
850
|
+
.slice(0, MAX_LSP_RESULT_ITEMS)
|
|
851
|
+
.map((span) => ({ range: scriptRange(document.text, span), kind: 1 }));
|
|
852
|
+
respond(message.id, locations);
|
|
853
|
+
break;
|
|
854
|
+
}
|
|
855
|
+
const path = pathOf(descriptor.uri);
|
|
856
|
+
const project = document ? await projectFor(document) : null;
|
|
857
|
+
const locations = document && path && project
|
|
858
|
+
? projectReferencesAt(project, path, offsetAt(document.text, position), true)
|
|
859
|
+
.filter((location) => location.path === path)
|
|
860
|
+
.slice(0, MAX_LSP_RESULT_ITEMS)
|
|
861
|
+
: [];
|
|
862
|
+
respond(message.id, project && path ? locations.map((location) => ({
|
|
863
|
+
range: lspRange(sourceFor(project, path), location.span),
|
|
864
|
+
kind: 1,
|
|
865
|
+
})) : []);
|
|
866
|
+
break;
|
|
867
|
+
}
|
|
868
|
+
case "textDocument/prepareRename": {
|
|
869
|
+
const descriptor = params?.textDocument;
|
|
870
|
+
const position = params?.position;
|
|
871
|
+
const document = documents.get(descriptor.uri);
|
|
872
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
873
|
+
if (document && script) {
|
|
874
|
+
const symbol = script.symbolAt(scriptOffsetAt(document.text, position));
|
|
875
|
+
respond(message.id, symbol ? { range: scriptRange(document.text, symbol), placeholder: symbol.name } : null);
|
|
876
|
+
break;
|
|
877
|
+
}
|
|
878
|
+
const path = pathOf(descriptor.uri);
|
|
879
|
+
const project = document ? await projectFor(document) : null;
|
|
880
|
+
const prepared = document && path && project ? projectPrepareRenameAt(project, path, offsetAt(document.text, position)) : null;
|
|
881
|
+
const selection = prepared?.edits[0];
|
|
882
|
+
respond(message.id, selection && project ? { range: lspRange(sourceFor(project, selection.path), selection.span), placeholder: prepared.placeholder } : null);
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
case "textDocument/rename": {
|
|
886
|
+
const descriptor = params?.textDocument;
|
|
887
|
+
const position = params?.position;
|
|
888
|
+
const newName = params?.newName;
|
|
889
|
+
const document = documents.get(descriptor.uri);
|
|
890
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
891
|
+
if (document && script) {
|
|
892
|
+
const renamed = script.renameAt(scriptOffsetAt(document.text, position), newName);
|
|
893
|
+
if (renamed.error)
|
|
894
|
+
respondError(message.id, renamed.error);
|
|
895
|
+
else if (renamed.edits.length > MAX_LSP_RESULT_ITEMS)
|
|
896
|
+
respondError(message.id, `Rename affects more than ${MAX_LSP_RESULT_ITEMS} locations`);
|
|
897
|
+
else
|
|
898
|
+
respond(message.id, {
|
|
899
|
+
changes: { [descriptor.uri]: renamed.edits.map((edit) => ({ range: scriptRange(document.text, edit), newText: edit.replacement })) },
|
|
900
|
+
});
|
|
901
|
+
break;
|
|
902
|
+
}
|
|
903
|
+
const path = pathOf(descriptor.uri);
|
|
904
|
+
const project = document ? await projectFor(document) : null;
|
|
905
|
+
const renamed = document && path && project ? projectRenameAt(project, path, offsetAt(document.text, position), newName) : "No renameable VelarScript symbol at this position";
|
|
906
|
+
if (typeof renamed === "string")
|
|
907
|
+
respondError(message.id, renamed);
|
|
908
|
+
else if (renamed.edits.length > MAX_LSP_RESULT_ITEMS)
|
|
909
|
+
respondError(message.id, `Rename affects more than ${MAX_LSP_RESULT_ITEMS} locations`);
|
|
910
|
+
else
|
|
911
|
+
respond(message.id, project ? workspaceEdit(project, renamed.edits, newName) : null);
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
case "textDocument/documentSymbol": {
|
|
915
|
+
const descriptor = params?.textDocument;
|
|
916
|
+
const document = documents.get(descriptor.uri);
|
|
917
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
918
|
+
if (document && script) {
|
|
919
|
+
respond(message.id, script.analysis().symbols.slice(0, MAX_LSP_RESULT_ITEMS).map((symbol) => ({
|
|
920
|
+
name: clipLspText(symbol.name),
|
|
921
|
+
detail: clipLspText(symbol.type),
|
|
922
|
+
kind: lspSymbolKind(symbol.kind),
|
|
923
|
+
range: scriptRange(document.text, symbol),
|
|
924
|
+
selectionRange: scriptRange(document.text, symbol),
|
|
925
|
+
})));
|
|
926
|
+
break;
|
|
927
|
+
}
|
|
928
|
+
const path = pathOf(descriptor.uri);
|
|
929
|
+
const project = document ? await projectFor(document) : null;
|
|
930
|
+
respond(message.id, path && project ? projectDocumentSymbols(project, path).slice(0, MAX_LSP_RESULT_ITEMS).map((symbol) => ({
|
|
931
|
+
name: clipLspText(symbol.name),
|
|
932
|
+
...(symbol.type ? { detail: clipLspText(symbol.type) } : {}),
|
|
933
|
+
kind: lspSymbolKind(symbol.presentationKind ?? symbol.kind),
|
|
934
|
+
range: lspRange(sourceFor(project, symbol.path), symbol.span),
|
|
935
|
+
selectionRange: lspRange(sourceFor(project, symbol.path), symbol.selectionSpan),
|
|
936
|
+
})) : []);
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
case "textDocument/signatureHelp": {
|
|
940
|
+
const descriptor = params?.textDocument;
|
|
941
|
+
const position = params?.position;
|
|
942
|
+
const document = documents.get(descriptor.uri);
|
|
943
|
+
if (scriptDocuments.has(descriptor.uri)) {
|
|
944
|
+
respond(message.id, null);
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
const path = pathOf(descriptor.uri);
|
|
948
|
+
const project = document ? await projectFor(document) : null;
|
|
949
|
+
const signature = document && path && project ? projectSignatureAt(project, path, offsetAt(document.text, position)) : null;
|
|
950
|
+
respond(message.id, signature ? {
|
|
951
|
+
signatures: [{ label: clipLspText(signature.label) }],
|
|
952
|
+
activeSignature: 0,
|
|
953
|
+
activeParameter: signature.activeParameter,
|
|
954
|
+
} : null);
|
|
955
|
+
break;
|
|
956
|
+
}
|
|
957
|
+
case "textDocument/inlayHint": {
|
|
958
|
+
const descriptor = params?.textDocument;
|
|
959
|
+
const range = params?.range;
|
|
960
|
+
const document = documents.get(descriptor.uri);
|
|
961
|
+
if (scriptDocuments.has(descriptor.uri)) {
|
|
962
|
+
respond(message.id, []);
|
|
963
|
+
break;
|
|
964
|
+
}
|
|
965
|
+
const path = pathOf(descriptor.uri);
|
|
966
|
+
const project = document ? await projectFor(document) : null;
|
|
967
|
+
respond(message.id, document && path && project ? projectInlayHints(project, path, document.text, range) : []);
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
case "textDocument/semanticTokens/full": {
|
|
971
|
+
const descriptor = params?.textDocument;
|
|
972
|
+
const document = documents.get(descriptor.uri);
|
|
973
|
+
const script = scriptDocuments.get(descriptor.uri);
|
|
974
|
+
if (document && script) {
|
|
975
|
+
respond(message.id, { data: scriptSemanticTokenData(document.text, script.analysis()) });
|
|
976
|
+
break;
|
|
977
|
+
}
|
|
978
|
+
const path = pathOf(descriptor.uri);
|
|
979
|
+
const project = document ? await projectFor(document) : null;
|
|
980
|
+
const tokens = path && project ? projectSemanticTokens(project, path).slice(0, MAX_LSP_RESULT_ITEMS) : [];
|
|
981
|
+
respond(message.id, { data: project && path ? semanticTokenData(sourceFor(project, path), tokens) : [] });
|
|
982
|
+
break;
|
|
983
|
+
}
|
|
984
|
+
case "textDocument/codeAction": {
|
|
985
|
+
const descriptor = params?.textDocument;
|
|
986
|
+
const context = params?.context;
|
|
987
|
+
const document = documents.get(descriptor.uri);
|
|
988
|
+
if (scriptDocuments.has(descriptor.uri)) {
|
|
989
|
+
respond(message.id, []);
|
|
990
|
+
break;
|
|
991
|
+
}
|
|
992
|
+
const acceptsQuickFix = !context?.only || context.only.some((kind) => kind === "quickfix" || kind.startsWith("quickfix."));
|
|
993
|
+
const fixPath = pathOf(descriptor.uri);
|
|
994
|
+
const fixProject = document && acceptsQuickFix ? await projectFor(document) : null;
|
|
995
|
+
respond(message.id, document && acceptsQuickFix
|
|
996
|
+
? quickFixes(document, fixProject && fixPath ? fixProject.modules.find((item) => item.inputPath === fixPath) ?? null : null, context?.diagnostics ?? [])
|
|
997
|
+
: []);
|
|
998
|
+
break;
|
|
999
|
+
}
|
|
1000
|
+
case "textDocument/formatting": {
|
|
1001
|
+
const descriptor = params?.textDocument;
|
|
1002
|
+
const document = documents.get(descriptor.uri);
|
|
1003
|
+
if (!document) {
|
|
1004
|
+
respond(message.id, []);
|
|
1005
|
+
break;
|
|
1006
|
+
}
|
|
1007
|
+
if (scriptDocuments.has(descriptor.uri)) {
|
|
1008
|
+
respond(message.id, []);
|
|
1009
|
+
break;
|
|
1010
|
+
}
|
|
1011
|
+
const project = await projectFor(document);
|
|
1012
|
+
const formatted = formatSource(document.text, { extensions: project?.compilerExtensions ?? [] });
|
|
1013
|
+
respond(message.id, formatted === document.text ? [] : [{ range: fullRange(document.text), newText: formatted }]);
|
|
1014
|
+
break;
|
|
1015
|
+
}
|
|
1016
|
+
default:
|
|
1017
|
+
if (message.id !== undefined)
|
|
1018
|
+
respondError(message.id, `Method not found: ${message.method ?? ""}`, -32601);
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
process.stdin.on("data", (chunk) => {
|
|
1022
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
1023
|
+
while (true) {
|
|
1024
|
+
const boundary = buffer.indexOf("\r\n\r\n");
|
|
1025
|
+
if (boundary === -1)
|
|
1026
|
+
break;
|
|
1027
|
+
const header = buffer.subarray(0, boundary).toString("ascii");
|
|
1028
|
+
const length = /(?:^|\r\n)Content-Length:\s*(\d+)/iu.exec(header);
|
|
1029
|
+
if (!length) {
|
|
1030
|
+
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "LSP message is missing a valid Content-Length header" } });
|
|
1031
|
+
process.exitCode = 1;
|
|
1032
|
+
process.stdin.pause();
|
|
1033
|
+
finish();
|
|
1034
|
+
break;
|
|
1035
|
+
}
|
|
1036
|
+
const size = Number(length[1]);
|
|
1037
|
+
if (!Number.isSafeInteger(size) || size > MAX_LSP_MESSAGE_BYTES) {
|
|
1038
|
+
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "LSP message exceeds the 16 MiB transport limit" } });
|
|
1039
|
+
process.exitCode = 1;
|
|
1040
|
+
process.stdin.pause();
|
|
1041
|
+
finish();
|
|
1042
|
+
break;
|
|
1043
|
+
}
|
|
1044
|
+
const end = boundary + 4 + size;
|
|
1045
|
+
if (buffer.length < end)
|
|
1046
|
+
break;
|
|
1047
|
+
const body = buffer.subarray(boundary + 4, end).toString("utf8");
|
|
1048
|
+
buffer = buffer.subarray(end);
|
|
1049
|
+
let parsed;
|
|
1050
|
+
try {
|
|
1051
|
+
parsed = JSON.parse(body);
|
|
1052
|
+
}
|
|
1053
|
+
catch {
|
|
1054
|
+
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Invalid JSON in LSP message" } });
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (!isRpcMessage(parsed)) {
|
|
1058
|
+
send({ jsonrpc: "2.0", id: null, error: { code: -32600, message: "Invalid LSP request" } });
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
const message = parsed;
|
|
1062
|
+
if (message.method === "$/cancelRequest") {
|
|
1063
|
+
const cancelId = message.params?.id;
|
|
1064
|
+
if (cancelId === null || typeof cancelId === "string" || (typeof cancelId === "number" && Number.isFinite(cancelId))) {
|
|
1065
|
+
const key = requestKey(cancelId);
|
|
1066
|
+
if (pendingRequests.has(key))
|
|
1067
|
+
cancelledRequests.add(key);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
else if (message.id !== undefined) {
|
|
1071
|
+
pendingRequests.add(requestKey(message.id));
|
|
1072
|
+
}
|
|
1073
|
+
queue = queue.then(() => handle(message)).catch((error) => {
|
|
1074
|
+
if (message.id !== undefined)
|
|
1075
|
+
respondError(message.id, hostErrorMessage(error), -32603);
|
|
1076
|
+
});
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
await Promise.race([new Promise((resolve) => process.stdin.once("end", resolve)), exitRequested]);
|
|
1080
|
+
await queue;
|
|
1081
|
+
if (diagnosticTask)
|
|
1082
|
+
await diagnosticTask;
|
|
1083
|
+
}
|
|
1084
|
+
function requestKey(id) {
|
|
1085
|
+
return `${typeof id}:${String(id)}`;
|
|
1086
|
+
}
|
|
1087
|
+
function isRpcMessage(value) {
|
|
1088
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1089
|
+
return false;
|
|
1090
|
+
const message = value;
|
|
1091
|
+
if (message.jsonrpc !== "2.0" || typeof message.method !== "string")
|
|
1092
|
+
return false;
|
|
1093
|
+
return message.id === undefined || message.id === null || typeof message.id === "string"
|
|
1094
|
+
|| (typeof message.id === "number" && Number.isFinite(message.id));
|
|
1095
|
+
}
|
|
1096
|
+
function clipLspText(value) {
|
|
1097
|
+
return value.length <= MAX_LSP_TEXT_CHARS ? value : `${value.slice(0, MAX_LSP_TEXT_CHARS - 1)}…`;
|
|
1098
|
+
}
|
|
1099
|
+
function boundedDiagnostics(source, diagnostics, notices) {
|
|
1100
|
+
const output = [];
|
|
1101
|
+
for (const diagnostic of diagnostics) {
|
|
1102
|
+
if (output.length >= MAX_LSP_RESULT_ITEMS)
|
|
1103
|
+
break;
|
|
1104
|
+
output.push(lspDiagnostic(source, diagnostic));
|
|
1105
|
+
}
|
|
1106
|
+
for (const notice of notices) {
|
|
1107
|
+
if (output.length >= MAX_LSP_RESULT_ITEMS)
|
|
1108
|
+
break;
|
|
1109
|
+
output.push(lspNotice(source, notice));
|
|
1110
|
+
}
|
|
1111
|
+
if (diagnostics.length + notices.length > MAX_LSP_RESULT_ITEMS) {
|
|
1112
|
+
if (output.length >= MAX_LSP_RESULT_ITEMS)
|
|
1113
|
+
output.pop();
|
|
1114
|
+
output.push(lspNotice(source, `Diagnostics were truncated to ${MAX_LSP_RESULT_ITEMS} items`));
|
|
1115
|
+
}
|
|
1116
|
+
return output;
|
|
1117
|
+
}
|
|
1118
|
+
function boundedScriptDiagnostics(text, analysis) {
|
|
1119
|
+
const diagnostics = analysis.diagnostics.slice(0, MAX_LSP_RESULT_ITEMS);
|
|
1120
|
+
const ends = diagnostics.map((diagnostic) => Math.max(diagnostic.start + 1, diagnostic.end));
|
|
1121
|
+
const positions = scriptPositionsAt(text, diagnostics.flatMap((diagnostic, index) => [diagnostic.start, ends[index]]));
|
|
1122
|
+
const output = diagnostics.map((diagnostic, index) => ({
|
|
1123
|
+
range: scriptRangeFromPositions(positions, diagnostic.start, ends[index]),
|
|
1124
|
+
severity: diagnostic.severity === "error" ? 1 : 2,
|
|
1125
|
+
code: diagnostic.code,
|
|
1126
|
+
source: "velar-script",
|
|
1127
|
+
message: clipLspText(diagnostic.message),
|
|
1128
|
+
}));
|
|
1129
|
+
if (analysis.diagnostics.length > MAX_LSP_RESULT_ITEMS) {
|
|
1130
|
+
if (output.length >= MAX_LSP_RESULT_ITEMS)
|
|
1131
|
+
output.pop();
|
|
1132
|
+
output.push({
|
|
1133
|
+
range: scriptRange(text, { start: 0, end: Math.min(1, codePointCount(text, 0, text.length)) }),
|
|
1134
|
+
severity: 2,
|
|
1135
|
+
code: "SCRIPT9001",
|
|
1136
|
+
source: "velar-script",
|
|
1137
|
+
message: `Script diagnostics were truncated to ${MAX_LSP_RESULT_ITEMS} items`,
|
|
1138
|
+
});
|
|
1139
|
+
}
|
|
1140
|
+
return output;
|
|
1141
|
+
}
|
|
1142
|
+
function oversizedDiagnosticsFallback(params) {
|
|
1143
|
+
const input = params && typeof params === "object" && !Array.isArray(params)
|
|
1144
|
+
? params
|
|
1145
|
+
: {};
|
|
1146
|
+
return {
|
|
1147
|
+
jsonrpc: "2.0",
|
|
1148
|
+
method: "textDocument/publishDiagnostics",
|
|
1149
|
+
params: {
|
|
1150
|
+
uri: typeof input.uri === "string" ? input.uri : "",
|
|
1151
|
+
...(Number.isSafeInteger(input.version) ? { version: input.version } : {}),
|
|
1152
|
+
diagnostics: [{
|
|
1153
|
+
range: { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } },
|
|
1154
|
+
severity: 1,
|
|
1155
|
+
code: "VEL9003",
|
|
1156
|
+
source: "velar",
|
|
1157
|
+
message: "VelarScript diagnostics exceeded the 16 MiB LSP transport limit",
|
|
1158
|
+
}],
|
|
1159
|
+
},
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
function lspDiagnostic(source, item) {
|
|
1163
|
+
const start = lspSourcePosition(source, item.span.start);
|
|
1164
|
+
const end = lspSourcePosition(source, Math.max(item.span.start + 1, item.span.end));
|
|
1165
|
+
return {
|
|
1166
|
+
range: {
|
|
1167
|
+
start,
|
|
1168
|
+
end,
|
|
1169
|
+
},
|
|
1170
|
+
severity: 1,
|
|
1171
|
+
code: item.code,
|
|
1172
|
+
source: "velar",
|
|
1173
|
+
message: clipLspText(item.message),
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
function lspNotice(source, message) {
|
|
1177
|
+
const start = lspSourcePosition(source, 0);
|
|
1178
|
+
const end = lspSourcePosition(source, Math.min(1, source.text.length));
|
|
1179
|
+
return {
|
|
1180
|
+
range: {
|
|
1181
|
+
start,
|
|
1182
|
+
end,
|
|
1183
|
+
},
|
|
1184
|
+
severity: 3,
|
|
1185
|
+
code: "VEL9002",
|
|
1186
|
+
source: "velar",
|
|
1187
|
+
message: clipLspText(message),
|
|
1188
|
+
};
|
|
1189
|
+
}
|
|
1190
|
+
async function hover(document, position, project) {
|
|
1191
|
+
const offset = offsetAt(document.text, position);
|
|
1192
|
+
const word = wordAt(document.text, offset);
|
|
1193
|
+
if (!word)
|
|
1194
|
+
return null;
|
|
1195
|
+
const path = pathOf(document.uri);
|
|
1196
|
+
const symbol = path && project ? projectSymbolAt(project, path, offset) : null;
|
|
1197
|
+
if (symbol) {
|
|
1198
|
+
const declaration = symbol.kind === "variable" ? (symbol.mutable ? "let" : "const") : symbol.kind;
|
|
1199
|
+
const type = symbol.type ? `: ${symbol.type}` : "";
|
|
1200
|
+
const documentation = symbol.documentation ? `\n\n${symbol.documentation}` : "";
|
|
1201
|
+
return { contents: { kind: "markdown", value: clipLspText(`\`\`${declaration} ${symbol.name}${type}\`\`${documentation}`) } };
|
|
1202
|
+
}
|
|
1203
|
+
const keyword = keywordDocumentation.get(word) ?? extensionDocumentation(project, "keywordDocumentation", word);
|
|
1204
|
+
if (keyword)
|
|
1205
|
+
return { contents: { kind: "markdown", value: `\`\`${word}\`\`\n\n${keyword}` } };
|
|
1206
|
+
const expression = path && project ? projectExpressionAt(project, path, offset) : null;
|
|
1207
|
+
if (expression?.memberName) {
|
|
1208
|
+
const declaration = expression.callable ? "method" : "field";
|
|
1209
|
+
const member = path && project ? projectMemberSymbolAt(project, path, offset) : null;
|
|
1210
|
+
const documentation = member?.documentation ? `\n\n${member.documentation}` : "";
|
|
1211
|
+
return { contents: { kind: "markdown", value: clipLspText(`\`\`${declaration} ${expression.memberName}: ${expression.type}\`\`${documentation}`) } };
|
|
1212
|
+
}
|
|
1213
|
+
const member = path && project ? projectMemberSymbolAt(project, path, offset) : null;
|
|
1214
|
+
if (member) {
|
|
1215
|
+
const type = member.type ? `: ${member.type}` : "";
|
|
1216
|
+
const documentation = member.documentation ? `\n\n${member.documentation}` : "";
|
|
1217
|
+
return { contents: { kind: "markdown", value: clipLspText(`\`\`${member.kind} ${member.name}${type}\`\`${documentation}`) } };
|
|
1218
|
+
}
|
|
1219
|
+
const builtinType = builtinTypeDocumentation.get(word) ?? extensionDocumentation(project, "typeDocumentation", word);
|
|
1220
|
+
if (builtinType)
|
|
1221
|
+
return { contents: { kind: "markdown", value: `\`\`${word}\`\`\n\n${builtinType}` } };
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
function lspLocation(project, path, span) {
|
|
1225
|
+
return { uri: pathToFileURL(path).href, range: lspRange(sourceFor(project, path), span) };
|
|
1226
|
+
}
|
|
1227
|
+
function lspRange(source, span) {
|
|
1228
|
+
return { start: lspSourcePosition(source, span.start), end: lspSourcePosition(source, span.end) };
|
|
1229
|
+
}
|
|
1230
|
+
function sourceFor(project, path) {
|
|
1231
|
+
const module = project.modules.find((item) => item.inputPath === path);
|
|
1232
|
+
if (!module)
|
|
1233
|
+
throw new Error(`VelarScript project has no source for ${path}`);
|
|
1234
|
+
return module.result.source;
|
|
1235
|
+
}
|
|
1236
|
+
function workspaceEdit(project, edits, newText) {
|
|
1237
|
+
const changes = {};
|
|
1238
|
+
for (const edit of edits) {
|
|
1239
|
+
const uri = pathToFileURL(edit.path).href;
|
|
1240
|
+
(changes[uri] ??= []).push({ range: lspRange(sourceFor(project, edit.path), edit.span), newText: edit.replacement ?? newText });
|
|
1241
|
+
}
|
|
1242
|
+
return { changes };
|
|
1243
|
+
}
|
|
1244
|
+
function semanticTokenData(source, tokens) {
|
|
1245
|
+
const data = [];
|
|
1246
|
+
let previousLine = 0;
|
|
1247
|
+
let previousCharacter = 0;
|
|
1248
|
+
for (const token of tokens) {
|
|
1249
|
+
const start = lspSourcePosition(source, token.span.start);
|
|
1250
|
+
const end = lspSourcePosition(source, token.span.end);
|
|
1251
|
+
if (start.line !== end.line || end.character <= start.character)
|
|
1252
|
+
continue;
|
|
1253
|
+
const line = start.line;
|
|
1254
|
+
const character = start.character;
|
|
1255
|
+
const deltaLine = line - previousLine;
|
|
1256
|
+
const deltaCharacter = deltaLine === 0 ? character - previousCharacter : character;
|
|
1257
|
+
const tokenType = semanticTokenTypes.indexOf(token.type);
|
|
1258
|
+
if (tokenType < 0 || deltaLine < 0 || deltaCharacter < 0)
|
|
1259
|
+
continue;
|
|
1260
|
+
const modifiers = token.modifiers.reduce((bits, modifier) => {
|
|
1261
|
+
const index = semanticTokenModifiers.indexOf(modifier);
|
|
1262
|
+
return index < 0 ? bits : bits | (1 << index);
|
|
1263
|
+
}, 0);
|
|
1264
|
+
data.push(deltaLine, deltaCharacter, end.character - start.character, tokenType, modifiers);
|
|
1265
|
+
previousLine = line;
|
|
1266
|
+
previousCharacter = character;
|
|
1267
|
+
}
|
|
1268
|
+
return data;
|
|
1269
|
+
}
|
|
1270
|
+
function scriptSemanticTokenData(text, analysis) {
|
|
1271
|
+
const tokens = analysis.tokens.slice(0, MAX_LSP_RESULT_ITEMS);
|
|
1272
|
+
const positions = scriptPositionsAt(text, tokens.flatMap((token) => [token.start, token.end]));
|
|
1273
|
+
const declarations = new Map(analysis.symbols.map((symbol) => [`${symbol.start}:${symbol.end}`, symbol]));
|
|
1274
|
+
const references = new Map(analysis.references.map((reference) => [`${reference.start}:${reference.end}`, reference]));
|
|
1275
|
+
const symbols = new Map(analysis.symbols.map((symbol) => [symbol.id, symbol]));
|
|
1276
|
+
const data = [];
|
|
1277
|
+
let previousLine = 0;
|
|
1278
|
+
let previousCharacter = 0;
|
|
1279
|
+
for (const token of tokens) {
|
|
1280
|
+
const key = `${token.start}:${token.end}`;
|
|
1281
|
+
const declaration = declarations.get(key);
|
|
1282
|
+
const reference = references.get(key);
|
|
1283
|
+
const symbol = declaration ?? (reference ? symbols.get(reference.symbolId) : undefined);
|
|
1284
|
+
const type = symbol ? scriptSemanticSymbolType(symbol.kind) : scriptLexicalTokenType(token.kind);
|
|
1285
|
+
if (!type)
|
|
1286
|
+
continue;
|
|
1287
|
+
const range = scriptRangeFromPositions(positions, token.start, token.end);
|
|
1288
|
+
if (range.start.line !== range.end.line || range.end.character <= range.start.character)
|
|
1289
|
+
continue;
|
|
1290
|
+
const deltaLine = range.start.line - previousLine;
|
|
1291
|
+
const deltaCharacter = deltaLine === 0 ? range.start.character - previousCharacter : range.start.character;
|
|
1292
|
+
const tokenType = semanticTokenTypes.indexOf(type);
|
|
1293
|
+
if (tokenType < 0 || deltaLine < 0 || deltaCharacter < 0)
|
|
1294
|
+
continue;
|
|
1295
|
+
const modifiers = (declaration ? 1 << semanticTokenModifiers.indexOf("declaration") : 0)
|
|
1296
|
+
| (symbol && (symbol.kind === "constant" || symbol.kind === "import") ? 1 << semanticTokenModifiers.indexOf("readonly") : 0);
|
|
1297
|
+
data.push(deltaLine, deltaCharacter, range.end.character - range.start.character, tokenType, modifiers);
|
|
1298
|
+
previousLine = range.start.line;
|
|
1299
|
+
previousCharacter = range.start.character;
|
|
1300
|
+
}
|
|
1301
|
+
return data;
|
|
1302
|
+
}
|
|
1303
|
+
function scriptSemanticSymbolType(kind) {
|
|
1304
|
+
switch (kind) {
|
|
1305
|
+
case "class": return "class";
|
|
1306
|
+
case "interface": return "interface";
|
|
1307
|
+
case "type": return "type";
|
|
1308
|
+
case "enum": return "enum";
|
|
1309
|
+
case "function": return "function";
|
|
1310
|
+
case "parameter": return "parameter";
|
|
1311
|
+
default: return "variable";
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
function scriptLexicalTokenType(kind) {
|
|
1315
|
+
switch (kind) {
|
|
1316
|
+
case "comment": return "comment";
|
|
1317
|
+
case "string":
|
|
1318
|
+
case "template": return "string";
|
|
1319
|
+
case "keyword": return "keyword";
|
|
1320
|
+
case "number": return "number";
|
|
1321
|
+
case "regexp": return "regexp";
|
|
1322
|
+
case "operator": return "operator";
|
|
1323
|
+
case "identifier": return "variable";
|
|
1324
|
+
default: return null;
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* D38 §48: an editor quick fix is the same mechanical rewrite `velar fix`
|
|
1329
|
+
* applies, read from the diagnostic that named it. The compiler registers the
|
|
1330
|
+
* rewrite where it reports the diagnostic, so the editor never re-derives one
|
|
1331
|
+
* from message text and the two surfaces can never drift apart.
|
|
1332
|
+
*/
|
|
1333
|
+
function quickFixes(document, module, diagnostics) {
|
|
1334
|
+
if (!module)
|
|
1335
|
+
return [];
|
|
1336
|
+
const source = module.result.source;
|
|
1337
|
+
const registered = module.result.diagnostics
|
|
1338
|
+
.filter((item) => item.fix && item.fix.edits.length > 0)
|
|
1339
|
+
.map((item) => ({ code: item.code, range: lspRange(source, boundedDiagnosticSpan(source, item.span)), fix: item.fix }));
|
|
1340
|
+
if (registered.length === 0)
|
|
1341
|
+
return [];
|
|
1342
|
+
const actions = [];
|
|
1343
|
+
for (const value of diagnostics.slice(0, MAX_LSP_RESULT_ITEMS)) {
|
|
1344
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1345
|
+
continue;
|
|
1346
|
+
const diagnostic = value;
|
|
1347
|
+
if (!diagnostic.range || typeof diagnostic.range !== "object" || Array.isArray(diagnostic.range))
|
|
1348
|
+
continue;
|
|
1349
|
+
const range = diagnostic.range;
|
|
1350
|
+
const match = registered.find((item) => item.code === diagnostic.code && sameLspRange(item.range, range));
|
|
1351
|
+
if (!match)
|
|
1352
|
+
continue;
|
|
1353
|
+
actions.push({
|
|
1354
|
+
title: match.fix.title,
|
|
1355
|
+
kind: "quickfix",
|
|
1356
|
+
isPreferred: true,
|
|
1357
|
+
edit: {
|
|
1358
|
+
changes: {
|
|
1359
|
+
[document.uri]: match.fix.edits.map((edit) => ({ range: lspRange(source, edit.span), newText: edit.text })),
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
});
|
|
1363
|
+
}
|
|
1364
|
+
return actions;
|
|
1365
|
+
}
|
|
1366
|
+
function boundedDiagnosticSpan(source, span) {
|
|
1367
|
+
return { start: span.start, end: Math.max(span.start + 1, span.end) > source.text.length ? span.end : Math.max(span.start + 1, span.end) };
|
|
1368
|
+
}
|
|
1369
|
+
function sameLspRange(left, right) {
|
|
1370
|
+
return left.start.line === right.start?.line && left.start.character === right.start?.character
|
|
1371
|
+
&& left.end.line === right.end?.line && left.end.character === right.end?.character;
|
|
1372
|
+
}
|
|
1373
|
+
function projectInlayHints(project, path, text, range) {
|
|
1374
|
+
const module = project.modules.find((item) => item.inputPath === path);
|
|
1375
|
+
if (!module)
|
|
1376
|
+
return [];
|
|
1377
|
+
const start = range ? offsetAt(text, range.start) : 0;
|
|
1378
|
+
const end = range ? offsetAt(text, range.end) : text.length;
|
|
1379
|
+
const hints = [];
|
|
1380
|
+
for (const symbol of module.result.semanticIndex.symbols) {
|
|
1381
|
+
if (hints.length >= MAX_LSP_RESULT_ITEMS)
|
|
1382
|
+
break;
|
|
1383
|
+
if (!symbol.sourceTypeHint || !symbol.type || symbol.type.length > 1024)
|
|
1384
|
+
continue;
|
|
1385
|
+
if (symbol.selectionSpan.end < start || symbol.selectionSpan.end > end)
|
|
1386
|
+
continue;
|
|
1387
|
+
const declarationEnd = lineEndAt(text, symbol.selectionSpan.end);
|
|
1388
|
+
const assignment = text.indexOf("=", symbol.selectionSpan.end);
|
|
1389
|
+
if (assignment !== -1 && assignment < declarationEnd && text.slice(symbol.selectionSpan.end, assignment).includes(":"))
|
|
1390
|
+
continue;
|
|
1391
|
+
const location = lspSourcePosition(module.result.source, symbol.selectionSpan.end);
|
|
1392
|
+
hints.push({
|
|
1393
|
+
position: location,
|
|
1394
|
+
label: `: ${symbol.type}`,
|
|
1395
|
+
kind: 1,
|
|
1396
|
+
paddingRight: true,
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1399
|
+
return hints;
|
|
1400
|
+
}
|
|
1401
|
+
function lspSymbolKind(kind) {
|
|
1402
|
+
if (kind.startsWith("extension:class:"))
|
|
1403
|
+
return 5;
|
|
1404
|
+
if (kind.startsWith("extension:type:"))
|
|
1405
|
+
return 11;
|
|
1406
|
+
if (kind.startsWith("extension:function:"))
|
|
1407
|
+
return 12;
|
|
1408
|
+
if (kind.startsWith("extension:variable:") || kind.startsWith("extension:parameter:"))
|
|
1409
|
+
return 13;
|
|
1410
|
+
switch (kind) {
|
|
1411
|
+
case "class": return 5;
|
|
1412
|
+
case "interface": return 11;
|
|
1413
|
+
case "method": return 6;
|
|
1414
|
+
case "field": return 8;
|
|
1415
|
+
case "enum": return 10;
|
|
1416
|
+
case "type": return 11;
|
|
1417
|
+
case "enum-member": return 22;
|
|
1418
|
+
case "function": return 12;
|
|
1419
|
+
case "variable":
|
|
1420
|
+
case "parameter":
|
|
1421
|
+
case "import":
|
|
1422
|
+
case "catch": return 13;
|
|
1423
|
+
default: return 13;
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
function lspCompletionKind(kind) {
|
|
1427
|
+
if (kind.startsWith("extension:function:"))
|
|
1428
|
+
return 3;
|
|
1429
|
+
if (kind.startsWith("extension:variable:") || kind.startsWith("extension:parameter:"))
|
|
1430
|
+
return 6;
|
|
1431
|
+
if (kind.startsWith("extension:class:"))
|
|
1432
|
+
return 7;
|
|
1433
|
+
if (kind.startsWith("extension:type:"))
|
|
1434
|
+
return 8;
|
|
1435
|
+
switch (kind) {
|
|
1436
|
+
case "method": return 2;
|
|
1437
|
+
case "function": return 3;
|
|
1438
|
+
case "field": return 5;
|
|
1439
|
+
case "variable":
|
|
1440
|
+
case "parameter":
|
|
1441
|
+
case "catch":
|
|
1442
|
+
case "import": return 6;
|
|
1443
|
+
case "class": return 7;
|
|
1444
|
+
case "interface": return 8;
|
|
1445
|
+
case "constant": return 6;
|
|
1446
|
+
case "type": return 8;
|
|
1447
|
+
case "enum": return 13;
|
|
1448
|
+
case "enum-member": return 20;
|
|
1449
|
+
default: return 1;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
function pathOf(uri) {
|
|
1453
|
+
const path = rawPathOf(uri);
|
|
1454
|
+
return path && (!confinedWorkspaceRoot || withinWorkspaceRoot(confinedWorkspaceRoot, path)) ? path : null;
|
|
1455
|
+
}
|
|
1456
|
+
function rawPathOf(uri) {
|
|
1457
|
+
try {
|
|
1458
|
+
return uri.startsWith("file:") ? fileURLToPath(uri) : null;
|
|
1459
|
+
}
|
|
1460
|
+
catch {
|
|
1461
|
+
return null;
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
async function authorizedPathOf(uri) {
|
|
1465
|
+
const path = pathOf(uri);
|
|
1466
|
+
if (!path || !confinedCanonicalRoot)
|
|
1467
|
+
return path;
|
|
1468
|
+
try {
|
|
1469
|
+
return await canonicalPathWithinCanonicalRoot(confinedCanonicalRoot, path) ? path : null;
|
|
1470
|
+
}
|
|
1471
|
+
catch {
|
|
1472
|
+
return null;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
function applyContentChanges(text, changes) {
|
|
1476
|
+
let result = text;
|
|
1477
|
+
for (const change of changes) {
|
|
1478
|
+
if (!change.range)
|
|
1479
|
+
result = change.text;
|
|
1480
|
+
else {
|
|
1481
|
+
const start = offsetAt(result, change.range.start);
|
|
1482
|
+
const end = offsetAt(result, change.range.end);
|
|
1483
|
+
result = result.slice(0, start) + change.text + result.slice(end);
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return result;
|
|
1487
|
+
}
|
|
1488
|
+
function scriptTextEdit(previous, next) {
|
|
1489
|
+
if (previous === next)
|
|
1490
|
+
return null;
|
|
1491
|
+
const before = Array.from(previous);
|
|
1492
|
+
const after = Array.from(next);
|
|
1493
|
+
const limit = Math.min(before.length, after.length);
|
|
1494
|
+
let prefix = 0;
|
|
1495
|
+
while (prefix < limit && before[prefix] === after[prefix])
|
|
1496
|
+
prefix += 1;
|
|
1497
|
+
let suffix = 0;
|
|
1498
|
+
while (suffix < limit - prefix && before[before.length - suffix - 1] === after[after.length - suffix - 1])
|
|
1499
|
+
suffix += 1;
|
|
1500
|
+
return {
|
|
1501
|
+
start: prefix,
|
|
1502
|
+
end: before.length - suffix,
|
|
1503
|
+
replacement: after.slice(prefix, after.length - suffix).join(""),
|
|
1504
|
+
};
|
|
1505
|
+
}
|
|
1506
|
+
function offsetAt(text, position) {
|
|
1507
|
+
const requestedLine = Number.isSafeInteger(position?.line) && position.line >= 0
|
|
1508
|
+
? position.line
|
|
1509
|
+
: 0;
|
|
1510
|
+
const requestedCharacter = Number.isSafeInteger(position?.character) && position.character >= 0
|
|
1511
|
+
? position.character
|
|
1512
|
+
: 0;
|
|
1513
|
+
let offset = 0;
|
|
1514
|
+
for (let line = 0; line < requestedLine && offset < text.length; line += 1) {
|
|
1515
|
+
const next = nextLineStart(text, offset);
|
|
1516
|
+
offset = next === null ? text.length : next;
|
|
1517
|
+
}
|
|
1518
|
+
const end = lineEndAt(text, offset);
|
|
1519
|
+
return activePositionEncoding === "utf-16"
|
|
1520
|
+
? Math.min(end, offset + requestedCharacter)
|
|
1521
|
+
: codeUnitOffsetAt(text, offset, end, requestedCharacter);
|
|
1522
|
+
}
|
|
1523
|
+
function scriptOffsetAt(text, position) {
|
|
1524
|
+
return codePointCount(text, 0, offsetAt(text, position));
|
|
1525
|
+
}
|
|
1526
|
+
function scriptRange(text, span) {
|
|
1527
|
+
const positions = scriptPositionsAt(text, [span.start, span.end]);
|
|
1528
|
+
return scriptRangeFromPositions(positions, span.start, span.end);
|
|
1529
|
+
}
|
|
1530
|
+
function scriptRangeFromPositions(positions, start, end) {
|
|
1531
|
+
const startPosition = positions.get(start);
|
|
1532
|
+
const endPosition = positions.get(end);
|
|
1533
|
+
if (!startPosition || !endPosition)
|
|
1534
|
+
throw new Error("Script coordinate map is incomplete");
|
|
1535
|
+
return { start: startPosition, end: endPosition };
|
|
1536
|
+
}
|
|
1537
|
+
function scriptPositionsAt(text, offsets) {
|
|
1538
|
+
const requested = [...new Set(offsets.map((offset) => Math.max(0, offset)))].sort((left, right) => left - right);
|
|
1539
|
+
const output = new Map();
|
|
1540
|
+
let codeUnit = 0;
|
|
1541
|
+
let codePoint = 0;
|
|
1542
|
+
let line = 0;
|
|
1543
|
+
let lineCodeUnit = 0;
|
|
1544
|
+
let lineCodePoint = 0;
|
|
1545
|
+
let pendingCarriageReturn = false;
|
|
1546
|
+
for (const requestedOffset of requested) {
|
|
1547
|
+
while (codePoint < requestedOffset && codeUnit < text.length) {
|
|
1548
|
+
const value = text.codePointAt(codeUnit);
|
|
1549
|
+
const width = value > 0xffff ? 2 : 1;
|
|
1550
|
+
const character = text[codeUnit];
|
|
1551
|
+
codeUnit += width;
|
|
1552
|
+
codePoint += 1;
|
|
1553
|
+
if (pendingCarriageReturn) {
|
|
1554
|
+
pendingCarriageReturn = false;
|
|
1555
|
+
if (character === "\n") {
|
|
1556
|
+
line += 1;
|
|
1557
|
+
lineCodeUnit = codeUnit;
|
|
1558
|
+
lineCodePoint = codePoint;
|
|
1559
|
+
continue;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
if (character === "\r") {
|
|
1563
|
+
if (text[codeUnit] === "\n")
|
|
1564
|
+
pendingCarriageReturn = true;
|
|
1565
|
+
else {
|
|
1566
|
+
line += 1;
|
|
1567
|
+
lineCodeUnit = codeUnit;
|
|
1568
|
+
lineCodePoint = codePoint;
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1571
|
+
else if (character === "\n") {
|
|
1572
|
+
line += 1;
|
|
1573
|
+
lineCodeUnit = codeUnit;
|
|
1574
|
+
lineCodePoint = codePoint;
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
const boundedOffset = Math.min(requestedOffset, codePoint);
|
|
1578
|
+
output.set(requestedOffset, {
|
|
1579
|
+
line,
|
|
1580
|
+
character: activePositionEncoding === "utf-16" ? codeUnit - lineCodeUnit : boundedOffset - lineCodePoint,
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
return output;
|
|
1584
|
+
}
|
|
1585
|
+
function positionAt(text, requestedOffset) {
|
|
1586
|
+
const offset = Math.max(0, Math.min(text.length, requestedOffset));
|
|
1587
|
+
let line = 0;
|
|
1588
|
+
let lineStart = 0;
|
|
1589
|
+
for (let index = 0; index < offset; index += 1) {
|
|
1590
|
+
const character = text[index];
|
|
1591
|
+
if (character === "\r") {
|
|
1592
|
+
const breakEnd = text[index + 1] === "\n" ? index + 2 : index + 1;
|
|
1593
|
+
if (breakEnd > offset)
|
|
1594
|
+
break;
|
|
1595
|
+
line += 1;
|
|
1596
|
+
lineStart = breakEnd;
|
|
1597
|
+
index = breakEnd - 1;
|
|
1598
|
+
}
|
|
1599
|
+
else if (character === "\n") {
|
|
1600
|
+
line += 1;
|
|
1601
|
+
lineStart = index + 1;
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
return { line, character: activePositionEncoding === "utf-16" ? offset - lineStart : codePointCount(text, lineStart, offset) };
|
|
1605
|
+
}
|
|
1606
|
+
function requestedPositionEncoding(params) {
|
|
1607
|
+
const capabilities = params?.capabilities;
|
|
1608
|
+
if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities))
|
|
1609
|
+
return "utf-16";
|
|
1610
|
+
const general = capabilities.general;
|
|
1611
|
+
if (!general || typeof general !== "object" || Array.isArray(general))
|
|
1612
|
+
return "utf-16";
|
|
1613
|
+
const encodings = general.positionEncodings;
|
|
1614
|
+
return Array.isArray(encodings) && encodings.includes("utf-32") ? "utf-32" : "utf-16";
|
|
1615
|
+
}
|
|
1616
|
+
function requestedWorkspaceRoots(params) {
|
|
1617
|
+
const roots = [];
|
|
1618
|
+
const folders = params?.workspaceFolders;
|
|
1619
|
+
if (Array.isArray(folders)) {
|
|
1620
|
+
for (const folder of folders) {
|
|
1621
|
+
if (!folder || typeof folder !== "object" || Array.isArray(folder))
|
|
1622
|
+
continue;
|
|
1623
|
+
const uri = folder.uri;
|
|
1624
|
+
if (typeof uri !== "string")
|
|
1625
|
+
continue;
|
|
1626
|
+
const path = rawPathOf(uri);
|
|
1627
|
+
if (!path)
|
|
1628
|
+
throw new TypeError("LSP workspace folders must use file URLs");
|
|
1629
|
+
roots.push(path);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
if (roots.length === 0 && typeof params?.rootUri === "string") {
|
|
1633
|
+
const path = rawPathOf(params.rootUri);
|
|
1634
|
+
if (!path)
|
|
1635
|
+
throw new TypeError("LSP rootUri must use a file URL");
|
|
1636
|
+
roots.push(path);
|
|
1637
|
+
}
|
|
1638
|
+
if (roots.length === 0 && typeof params?.rootPath === "string" && params.rootPath !== "")
|
|
1639
|
+
roots.push(params.rootPath);
|
|
1640
|
+
if (confinedWorkspaceRoot) {
|
|
1641
|
+
if (roots.some((root) => !withinWorkspaceRoot(confinedWorkspaceRoot, resolve(root)))) {
|
|
1642
|
+
throw new RangeError("LSP workspace roots must remain inside the Desktop project grant");
|
|
1643
|
+
}
|
|
1644
|
+
return [confinedWorkspaceRoot];
|
|
1645
|
+
}
|
|
1646
|
+
return [...new Set(roots)];
|
|
1647
|
+
}
|
|
1648
|
+
function configuredWorkspaceRoot() {
|
|
1649
|
+
const value = process.env.VELAR_LANGUAGE_SERVER_WORKSPACE_ROOT;
|
|
1650
|
+
return typeof value === "string" && value !== "" ? resolve(value) : null;
|
|
1651
|
+
}
|
|
1652
|
+
function configuredCanonicalRoot() {
|
|
1653
|
+
const value = process.env.VELAR_LANGUAGE_SERVER_CANONICAL_ROOT;
|
|
1654
|
+
return typeof value === "string" && value !== "" ? resolve(value) : null;
|
|
1655
|
+
}
|
|
1656
|
+
function withinWorkspaceRoot(root, path) {
|
|
1657
|
+
const value = relative(root, resolve(path));
|
|
1658
|
+
return value === "" || (!value.startsWith("..") && !isAbsolute(value));
|
|
1659
|
+
}
|
|
1660
|
+
function workspacePosition(position) {
|
|
1661
|
+
return {
|
|
1662
|
+
line: position.line,
|
|
1663
|
+
character: activePositionEncoding === "utf-16" ? position.utf16Character : position.utf32Character,
|
|
1664
|
+
};
|
|
1665
|
+
}
|
|
1666
|
+
async function mapBounded(values, concurrency, operation) {
|
|
1667
|
+
const results = new Array(values.length);
|
|
1668
|
+
let cursor = 0;
|
|
1669
|
+
const workers = Array.from({ length: Math.min(concurrency, values.length) }, async () => {
|
|
1670
|
+
while (cursor < values.length) {
|
|
1671
|
+
const index = cursor;
|
|
1672
|
+
cursor += 1;
|
|
1673
|
+
results[index] = await operation(values[index]);
|
|
1674
|
+
}
|
|
1675
|
+
});
|
|
1676
|
+
await Promise.all(workers);
|
|
1677
|
+
return results;
|
|
1678
|
+
}
|
|
1679
|
+
function lspSourcePosition(source, offset) {
|
|
1680
|
+
const location = source.location(offset);
|
|
1681
|
+
const line = location.line - 1;
|
|
1682
|
+
const start = source.lineStarts[line] ?? 0;
|
|
1683
|
+
const bounded = Math.max(start, Math.min(offset, source.text.length));
|
|
1684
|
+
return {
|
|
1685
|
+
line,
|
|
1686
|
+
character: activePositionEncoding === "utf-16" ? bounded - start : codePointCount(source.text, start, bounded),
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
function codePointCount(text, start, end) {
|
|
1690
|
+
let count = 0;
|
|
1691
|
+
for (let index = start; index < end; count += 1) {
|
|
1692
|
+
const point = text.codePointAt(index);
|
|
1693
|
+
index += point !== undefined && point > 0xffff ? 2 : 1;
|
|
1694
|
+
}
|
|
1695
|
+
return count;
|
|
1696
|
+
}
|
|
1697
|
+
function codeUnitOffsetAt(text, start, end, requested) {
|
|
1698
|
+
let index = start;
|
|
1699
|
+
for (let count = 0; count < requested && index < end; count += 1) {
|
|
1700
|
+
const point = text.codePointAt(index);
|
|
1701
|
+
index += point !== undefined && point > 0xffff ? 2 : 1;
|
|
1702
|
+
}
|
|
1703
|
+
return index;
|
|
1704
|
+
}
|
|
1705
|
+
function nextLineStart(text, start) {
|
|
1706
|
+
for (let index = start; index < text.length; index += 1) {
|
|
1707
|
+
if (text[index] === "\r")
|
|
1708
|
+
return index + (text[index + 1] === "\n" ? 2 : 1);
|
|
1709
|
+
if (text[index] === "\n")
|
|
1710
|
+
return index + 1;
|
|
1711
|
+
}
|
|
1712
|
+
return null;
|
|
1713
|
+
}
|
|
1714
|
+
function lineEndAt(text, start) {
|
|
1715
|
+
for (let index = start; index < text.length; index += 1) {
|
|
1716
|
+
if (text[index] === "\r" || text[index] === "\n")
|
|
1717
|
+
return index;
|
|
1718
|
+
}
|
|
1719
|
+
return text.length;
|
|
1720
|
+
}
|
|
1721
|
+
function wordAt(text, offset) {
|
|
1722
|
+
let start = offset;
|
|
1723
|
+
let end = offset;
|
|
1724
|
+
while (start > 0 && isSourceIdentifierPart(text[start - 1]))
|
|
1725
|
+
start -= 1;
|
|
1726
|
+
while (end < text.length && isSourceIdentifierPart(text[end]))
|
|
1727
|
+
end += 1;
|
|
1728
|
+
return text.slice(start, end);
|
|
1729
|
+
}
|
|
1730
|
+
function fullRange(text) {
|
|
1731
|
+
return { start: { line: 0, character: 0 }, end: positionAt(text, text.length) };
|
|
1732
|
+
}
|
|
1733
|
+
//# sourceMappingURL=language-server.js.map
|