brighterscript 1.0.0-alpha.20 → 1.0.0-alpha.23
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/CHANGELOG.md +134 -0
- package/README.md +6 -3
- package/bsconfig.schema.json +10 -2
- package/dist/BsConfig.d.ts +5 -0
- package/dist/Cache.d.ts +5 -1
- package/dist/Cache.js +6 -0
- package/dist/Cache.js.map +1 -1
- package/dist/DependencyGraph.d.ts +2 -2
- package/dist/DependencyGraph.js +15 -3
- package/dist/DependencyGraph.js.map +1 -1
- package/dist/DiagnosticCollection.d.ts +5 -3
- package/dist/DiagnosticCollection.js +10 -12
- package/dist/DiagnosticCollection.js.map +1 -1
- package/dist/DiagnosticFilterer.js +4 -3
- package/dist/DiagnosticFilterer.js.map +1 -1
- package/dist/DiagnosticMessages.d.ts +6 -1
- package/dist/DiagnosticMessages.js +9 -4
- package/dist/DiagnosticMessages.js.map +1 -1
- package/dist/LanguageServer.d.ts +48 -25
- package/dist/LanguageServer.js +303 -222
- package/dist/LanguageServer.js.map +1 -1
- package/dist/Program.d.ts +7 -3
- package/dist/Program.js +60 -22
- package/dist/Program.js.map +1 -1
- package/dist/ProgramBuilder.js +11 -11
- package/dist/ProgramBuilder.js.map +1 -1
- package/dist/Scope.d.ts +41 -15
- package/dist/Scope.js +86 -21
- package/dist/Scope.js.map +1 -1
- package/dist/SymbolTable.js +15 -0
- package/dist/SymbolTable.js.map +1 -1
- package/dist/XmlScope.d.ts +3 -3
- package/dist/astUtils/AstEditor.d.ts +32 -0
- package/dist/astUtils/AstEditor.js +132 -0
- package/dist/astUtils/AstEditor.js.map +1 -1
- package/dist/astUtils/AstEditor.spec.js +118 -34
- package/dist/astUtils/AstEditor.spec.js.map +1 -1
- package/dist/astUtils/creators.d.ts +6 -2
- package/dist/astUtils/creators.js +9 -2
- package/dist/astUtils/creators.js.map +1 -1
- package/dist/astUtils/reflection.d.ts +6 -3
- package/dist/astUtils/reflection.js +18 -8
- package/dist/astUtils/reflection.js.map +1 -1
- package/dist/astUtils/visitors.d.ts +74 -64
- package/dist/astUtils/visitors.js +30 -10
- package/dist/astUtils/visitors.js.map +1 -1
- package/dist/astUtils/visitors.spec.js +95 -3
- package/dist/astUtils/visitors.spec.js.map +1 -1
- package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.d.ts +7 -1
- package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js +55 -54
- package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.js.map +1 -1
- package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js +205 -17
- package/dist/bscPlugin/semanticTokens/BrsFileSemanticTokensProcessor.spec.js.map +1 -1
- package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js +13 -25
- package/dist/bscPlugin/transpile/BrsFilePreTranspileProcessor.js.map +1 -1
- package/dist/bscPlugin/validation/ScopeValidator.js +26 -20
- package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
- package/dist/cli.js +1 -0
- package/dist/cli.js.map +1 -1
- package/dist/files/BrsFile.Class.spec.js +167 -2
- package/dist/files/BrsFile.Class.spec.js.map +1 -1
- package/dist/files/BrsFile.d.ts +8 -0
- package/dist/files/BrsFile.js +48 -30
- package/dist/files/BrsFile.js.map +1 -1
- package/dist/files/BrsFile.spec.js +183 -0
- package/dist/files/BrsFile.spec.js.map +1 -1
- package/dist/files/XmlFile.d.ts +5 -5
- package/dist/files/XmlFile.js +5 -4
- package/dist/files/XmlFile.js.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +18 -2
- package/dist/parser/BrsTranspileState.d.ts +2 -0
- package/dist/parser/BrsTranspileState.js +5 -0
- package/dist/parser/BrsTranspileState.js.map +1 -1
- package/dist/parser/Expression.js +6 -23
- package/dist/parser/Expression.js.map +1 -1
- package/dist/parser/Parser.Class.spec.js.map +1 -1
- package/dist/parser/Parser.d.ts +8 -4
- package/dist/parser/Parser.js +40 -7
- package/dist/parser/Parser.js.map +1 -1
- package/dist/parser/Parser.spec.js +51 -7
- package/dist/parser/Parser.spec.js.map +1 -1
- package/dist/parser/Statement.d.ts +20 -20
- package/dist/parser/Statement.js +108 -90
- package/dist/parser/Statement.js.map +1 -1
- package/dist/parser/tests/statement/Enum.spec.js +39 -6
- package/dist/parser/tests/statement/Enum.spec.js.map +1 -1
- package/dist/parser/tests/statement/InterfaceStatement.spec.js +16 -2
- package/dist/parser/tests/statement/InterfaceStatement.spec.js.map +1 -1
- package/dist/types/EnumType.d.ts +22 -0
- package/dist/types/EnumType.js +55 -0
- package/dist/types/EnumType.js.map +1 -0
- package/dist/util.d.ts +6 -7
- package/dist/util.js +32 -24
- package/dist/util.js.map +1 -1
- package/dist/validators/ClassValidator.d.ts +4 -0
- package/dist/validators/ClassValidator.js +27 -7
- package/dist/validators/ClassValidator.js.map +1 -1
- package/package.json +11 -11
package/dist/LanguageServer.js
CHANGED
|
@@ -8,9 +8,9 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.CustomCommands = exports.LanguageServer = void 0;
|
|
10
10
|
require("array-flat-polyfill");
|
|
11
|
-
const
|
|
11
|
+
const fastGlob = require("fast-glob");
|
|
12
12
|
const path = require("path");
|
|
13
|
-
const
|
|
13
|
+
const roku_deploy_1 = require("roku-deploy");
|
|
14
14
|
const node_1 = require("vscode-languageserver/node");
|
|
15
15
|
const vscode_uri_1 = require("vscode-uri");
|
|
16
16
|
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
|
|
@@ -27,18 +27,18 @@ const SemanticTokenUtils_1 = require("./SemanticTokenUtils");
|
|
|
27
27
|
class LanguageServer {
|
|
28
28
|
constructor() {
|
|
29
29
|
this.connection = undefined;
|
|
30
|
-
this.
|
|
30
|
+
this.projects = [];
|
|
31
31
|
/**
|
|
32
32
|
* The number of milliseconds that should be used for language server typing debouncing
|
|
33
33
|
*/
|
|
34
34
|
this.debounceTimeout = 150;
|
|
35
35
|
/**
|
|
36
|
-
* These
|
|
37
|
-
* in any of the workspace projects.
|
|
38
|
-
* Basically these are single-file
|
|
36
|
+
* These projects are created on the fly whenever a file is opened that is not included
|
|
37
|
+
* in any of the workspace-based projects.
|
|
38
|
+
* Basically these are single-file projects to at least get parsing for standalone files.
|
|
39
39
|
* Also, they should only be created when the file is opened, and destroyed when the file is closed.
|
|
40
40
|
*/
|
|
41
|
-
this.
|
|
41
|
+
this.standaloneFileProjects = {};
|
|
42
42
|
this.hasConfigurationCapability = false;
|
|
43
43
|
/**
|
|
44
44
|
* Indicates whether the client supports workspace folders
|
|
@@ -51,6 +51,7 @@ class LanguageServer {
|
|
|
51
51
|
this.documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
|
|
52
52
|
this.keyedThrottler = new KeyedThrottler_1.KeyedThrottler(this.debounceTimeout);
|
|
53
53
|
this.validateThrottler = new Throttler_1.Throttler(0);
|
|
54
|
+
this.sendDiagnosticsThrottler = new Throttler_1.Throttler(0);
|
|
54
55
|
this.boundValidateAll = this.validateAll.bind(this);
|
|
55
56
|
this.diagnosticCollection = new DiagnosticCollection_1.DiagnosticCollection();
|
|
56
57
|
}
|
|
@@ -77,17 +78,11 @@ class LanguageServer {
|
|
|
77
78
|
// when the text document is first opened, when its content has changed,
|
|
78
79
|
// or when document is closed without saving (original contents are sent as a change)
|
|
79
80
|
//
|
|
80
|
-
this.documents.onDidChangeContent(
|
|
81
|
-
await this.validateTextDocument(change.document);
|
|
82
|
-
});
|
|
81
|
+
this.documents.onDidChangeContent(this.validateTextDocument.bind(this));
|
|
83
82
|
//whenever a document gets closed
|
|
84
|
-
this.documents.onDidClose(
|
|
85
|
-
await this.onDocumentClose(change.document);
|
|
86
|
-
});
|
|
83
|
+
this.documents.onDidClose(this.onDocumentClose.bind(this));
|
|
87
84
|
// This handler provides the initial list of the completion items.
|
|
88
|
-
this.connection.onCompletion(
|
|
89
|
-
return this.onCompletion(params.textDocument.uri, params.position);
|
|
90
|
-
});
|
|
85
|
+
this.connection.onCompletion(this.onCompletion.bind(this));
|
|
91
86
|
// This handler resolves additional information for the item selected in
|
|
92
87
|
// the completion list.
|
|
93
88
|
this.connection.onCompletionResolve(this.onCompletionResolve.bind(this));
|
|
@@ -169,43 +164,118 @@ class LanguageServer {
|
|
|
169
164
|
}
|
|
170
165
|
};
|
|
171
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Ask the client for the list of `files.exclude` patterns. Useful when determining if we should process a file
|
|
169
|
+
*/
|
|
170
|
+
async getWorkspaceExcludeGlobs(workspaceFolder) {
|
|
171
|
+
var _a;
|
|
172
|
+
//get any `files.exclude` globs to use to filter
|
|
173
|
+
let config = await this.connection.workspace.getConfiguration({
|
|
174
|
+
scopeUri: workspaceFolder,
|
|
175
|
+
section: 'files'
|
|
176
|
+
});
|
|
177
|
+
return Object
|
|
178
|
+
.keys((_a = config === null || config === void 0 ? void 0 : config.exclude) !== null && _a !== void 0 ? _a : {})
|
|
179
|
+
.filter(x => { var _a; return (_a = config === null || config === void 0 ? void 0 : config.exclude) === null || _a === void 0 ? void 0 : _a[x]; })
|
|
180
|
+
//vscode files.exclude patterns support ignoring folders without needing to add `**/*`. So for our purposes, we need to
|
|
181
|
+
//append **/* to everything without a file extension or magic at the end
|
|
182
|
+
.map(pattern => [
|
|
183
|
+
//send the pattern as-is (this handles weird cases and exact file matches)
|
|
184
|
+
pattern,
|
|
185
|
+
//treat the pattern as a directory (no harm in doing this because if it's a file, the pattern will just never match anything)
|
|
186
|
+
`${pattern}/**/*`
|
|
187
|
+
])
|
|
188
|
+
.flat(1)
|
|
189
|
+
.concat([
|
|
190
|
+
//always ignore projects from node_modules
|
|
191
|
+
'**/node_modules/**/*'
|
|
192
|
+
]);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Scan the workspace for all `bsconfig.json` files. If at least one is found, then only folders who have bsconfig.json are returned.
|
|
196
|
+
* If none are found, then the workspaceFolder itself is treated as a project
|
|
197
|
+
*/
|
|
198
|
+
async getProjectPaths(workspaceFolder) {
|
|
199
|
+
const excludes = (await this.getWorkspaceExcludeGlobs(workspaceFolder)).map(x => (0, util_1.standardizePath) `!${x}`);
|
|
200
|
+
const files = await roku_deploy_1.rokuDeploy.getFilePaths([
|
|
201
|
+
'**/bsconfig.json',
|
|
202
|
+
//exclude all files found in `files.exclude`
|
|
203
|
+
...excludes
|
|
204
|
+
], workspaceFolder);
|
|
205
|
+
//if we found at least one bsconfig.json, then ALL projects must have a bsconfig.json.
|
|
206
|
+
if (files.length > 0) {
|
|
207
|
+
return files.map(file => (0, util_1.standardizePath) `${path.dirname(file.src)}`);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
//treat the workspace folder as a brightscript project itself
|
|
211
|
+
return [workspaceFolder];
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Find all folders with bsconfig.json files in them, and treat each as a project.
|
|
216
|
+
* Treat workspaces that don't have a bsconfig.json as a project.
|
|
217
|
+
* Handle situations where bsconfig.json files were added or removed (to elevate/lower workspaceFolder projects accordingly)
|
|
218
|
+
* Leave existing projects alone if they are not affected by these changes
|
|
219
|
+
*/
|
|
220
|
+
async syncProjects() {
|
|
221
|
+
const workspacePaths = await this.getWorkspacePaths();
|
|
222
|
+
let projectPaths = (await Promise.all(workspacePaths.map(async (workspacePath) => {
|
|
223
|
+
const projectPaths = await this.getProjectPaths(workspacePath);
|
|
224
|
+
return projectPaths.map(projectPath => ({
|
|
225
|
+
projectPath: projectPath,
|
|
226
|
+
workspacePath: workspacePath
|
|
227
|
+
}));
|
|
228
|
+
}))).flat(1);
|
|
229
|
+
//delete projects not represented in the list
|
|
230
|
+
for (const project of this.getProjects()) {
|
|
231
|
+
if (!projectPaths.find(x => x.projectPath === project.projectPath)) {
|
|
232
|
+
this.removeProject(project);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
//exclude paths to projects we already have
|
|
236
|
+
projectPaths = projectPaths.filter(x => {
|
|
237
|
+
//only keep this project path if there's not a project with that path
|
|
238
|
+
return !this.projects.find(project => project.projectPath === x.projectPath);
|
|
239
|
+
});
|
|
240
|
+
//dedupe by project path
|
|
241
|
+
projectPaths = [
|
|
242
|
+
...projectPaths.reduce((acc, x) => acc.set(x.projectPath, x), new Map()).values()
|
|
243
|
+
];
|
|
244
|
+
//create missing projects
|
|
245
|
+
await Promise.all(projectPaths.map(x => this.createProject(x.projectPath, x.workspacePath)));
|
|
246
|
+
//flush diagnostics
|
|
247
|
+
await this.sendDiagnostics();
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Get all workspace paths from the client
|
|
251
|
+
*/
|
|
252
|
+
async getWorkspacePaths() {
|
|
253
|
+
var _a;
|
|
254
|
+
let workspaceFolders = (_a = await this.connection.workspace.getWorkspaceFolders()) !== null && _a !== void 0 ? _a : [];
|
|
255
|
+
return workspaceFolders.map((x) => {
|
|
256
|
+
return util_1.util.uriToPath(x.uri);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
172
259
|
/**
|
|
173
260
|
* Called when the client has finished initializing
|
|
174
261
|
* @param params
|
|
175
262
|
*/
|
|
176
263
|
async onInitialized() {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.initialWorkspacesCreated = workspaceCreatedDeferred.promise;
|
|
264
|
+
let projectCreatedDeferred = new deferred_1.Deferred();
|
|
265
|
+
this.initialProjectsCreated = projectCreatedDeferred.promise;
|
|
180
266
|
try {
|
|
181
267
|
if (this.hasConfigurationCapability) {
|
|
182
268
|
// Register for all configuration changes.
|
|
183
269
|
await this.connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
|
|
184
270
|
}
|
|
185
|
-
|
|
186
|
-
let workspaceFolders = (_a = await this.connection.workspace.getWorkspaceFolders()) !== null && _a !== void 0 ? _a : [];
|
|
187
|
-
let workspacePaths = workspaceFolders.map((x) => {
|
|
188
|
-
return util_1.util.uriToPath(x.uri);
|
|
189
|
-
});
|
|
190
|
-
await this.createWorkspaces(workspacePaths);
|
|
271
|
+
await this.syncProjects();
|
|
191
272
|
if (this.clientHasWorkspaceFolderCapability) {
|
|
192
273
|
this.connection.workspace.onDidChangeWorkspaceFolders(async (evt) => {
|
|
193
|
-
|
|
194
|
-
for (let removed of evt.removed) {
|
|
195
|
-
let workspacePath = util_1.util.uriToPath(removed.uri);
|
|
196
|
-
let workspace = this.workspaces.find((x) => x.workspacePath === workspacePath);
|
|
197
|
-
if (workspace) {
|
|
198
|
-
workspace.builder.dispose();
|
|
199
|
-
this.workspaces.splice(this.workspaces.indexOf(workspace), 1);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
//create programs for new workspace folders
|
|
203
|
-
await this.createWorkspaces(evt.added.map((x) => util_1.util.uriToPath(x.uri)));
|
|
274
|
+
await this.syncProjects();
|
|
204
275
|
});
|
|
205
276
|
}
|
|
206
|
-
await this.
|
|
207
|
-
|
|
208
|
-
await this.sendDiagnostics();
|
|
277
|
+
await this.waitAllProjectFirstRuns(false);
|
|
278
|
+
projectCreatedDeferred.resolve();
|
|
209
279
|
}
|
|
210
280
|
catch (e) {
|
|
211
281
|
this.sendCriticalFailure(`Critical failure during BrighterScript language server startup.
|
|
@@ -224,34 +294,25 @@ class LanguageServer {
|
|
|
224
294
|
/**
|
|
225
295
|
* Wait for all programs' first run to complete
|
|
226
296
|
*/
|
|
227
|
-
async
|
|
228
|
-
if (
|
|
229
|
-
await this.
|
|
297
|
+
async waitAllProjectFirstRuns(waitForFirstProject = true) {
|
|
298
|
+
if (waitForFirstProject) {
|
|
299
|
+
await this.initialProjectsCreated;
|
|
230
300
|
}
|
|
231
301
|
let status;
|
|
232
|
-
let
|
|
233
|
-
for (let workspace of workspaces) {
|
|
302
|
+
for (let project of this.getProjects()) {
|
|
234
303
|
try {
|
|
235
|
-
await
|
|
304
|
+
await project.firstRunPromise;
|
|
236
305
|
}
|
|
237
306
|
catch (e) {
|
|
238
307
|
status = 'critical-error';
|
|
239
308
|
//the first run failed...that won't change unless we reload the workspace, so replace with resolved promise
|
|
240
309
|
//so we don't show this error again
|
|
241
|
-
|
|
310
|
+
project.firstRunPromise = Promise.resolve();
|
|
242
311
|
this.sendCriticalFailure(`BrighterScript language server failed to start: \n${e.message}`);
|
|
243
312
|
}
|
|
244
313
|
}
|
|
245
314
|
this.connection.sendNotification('build-status', status ? status : 'success');
|
|
246
315
|
}
|
|
247
|
-
/**
|
|
248
|
-
* Create project for each new workspace. If the workspace is already known,
|
|
249
|
-
* it is skipped.
|
|
250
|
-
* @param workspaceFolders
|
|
251
|
-
*/
|
|
252
|
-
async createWorkspaces(workspacePaths) {
|
|
253
|
-
return Promise.all(workspacePaths.map(async (workspacePath) => this.createWorkspace(workspacePath)));
|
|
254
|
-
}
|
|
255
316
|
/**
|
|
256
317
|
* Event handler for when the program wants to load file contents.
|
|
257
318
|
* anytime the program wants to load a file, check with our in-memory document cache first
|
|
@@ -284,7 +345,7 @@ class LanguageServer {
|
|
|
284
345
|
return configFilePath;
|
|
285
346
|
}
|
|
286
347
|
else {
|
|
287
|
-
this.sendCriticalFailure(`Cannot find config file specified in user/workspace settings at '${configFilePath}'`);
|
|
348
|
+
this.sendCriticalFailure(`Cannot find config file specified in user / workspace settings at '${configFilePath}'`);
|
|
288
349
|
}
|
|
289
350
|
}
|
|
290
351
|
//default to config file path found in the root of the workspace
|
|
@@ -300,19 +361,27 @@ class LanguageServer {
|
|
|
300
361
|
//no config file could be found
|
|
301
362
|
return undefined;
|
|
302
363
|
}
|
|
303
|
-
async
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
if
|
|
364
|
+
async createProject(projectPath, workspacePath = projectPath) {
|
|
365
|
+
workspacePath !== null && workspacePath !== void 0 ? workspacePath : (workspacePath = projectPath);
|
|
366
|
+
let project = this.projects.find((x) => x.projectPath === projectPath);
|
|
367
|
+
//skip this project if we already have it
|
|
368
|
+
if (project) {
|
|
307
369
|
return;
|
|
308
370
|
}
|
|
309
371
|
let builder = new ProgramBuilder_1.ProgramBuilder();
|
|
372
|
+
//flush diagnostics every time the program finishes validating
|
|
373
|
+
builder.plugins.add({
|
|
374
|
+
name: 'bsc-language-server',
|
|
375
|
+
afterProgramValidate: () => {
|
|
376
|
+
void this.sendDiagnostics();
|
|
377
|
+
}
|
|
378
|
+
});
|
|
310
379
|
//prevent clearing the console on run...this isn't the CLI so we want to keep a full log of everything
|
|
311
380
|
builder.allowConsoleClearing = false;
|
|
312
381
|
//look for files in our in-memory cache before going to the file system
|
|
313
382
|
builder.addFileResolver(this.documentFileResolver.bind(this));
|
|
314
|
-
let configFilePath = await this.getConfigFilePath(
|
|
315
|
-
let cwd =
|
|
383
|
+
let configFilePath = await this.getConfigFilePath(projectPath);
|
|
384
|
+
let cwd = projectPath;
|
|
316
385
|
//if the config file exists, use it and its folder as cwd
|
|
317
386
|
if (configFilePath && await util_1.util.pathExists(configFilePath)) {
|
|
318
387
|
cwd = path.dirname(configFilePath);
|
|
@@ -333,22 +402,23 @@ class LanguageServer {
|
|
|
333
402
|
firstRunPromise.catch((err) => {
|
|
334
403
|
console.error(err);
|
|
335
404
|
});
|
|
336
|
-
let
|
|
405
|
+
let newProject = {
|
|
337
406
|
builder: builder,
|
|
338
407
|
firstRunPromise: firstRunPromise,
|
|
408
|
+
projectPath: projectPath,
|
|
339
409
|
workspacePath: workspacePath,
|
|
340
410
|
isFirstRunComplete: false,
|
|
341
411
|
isFirstRunSuccessful: false,
|
|
342
412
|
configFilePath: configFilePath,
|
|
343
|
-
|
|
413
|
+
isStandaloneFileProject: false
|
|
344
414
|
};
|
|
345
|
-
this.
|
|
415
|
+
this.projects.push(newProject);
|
|
346
416
|
await firstRunPromise.then(() => {
|
|
347
|
-
|
|
348
|
-
|
|
417
|
+
newProject.isFirstRunComplete = true;
|
|
418
|
+
newProject.isFirstRunSuccessful = true;
|
|
349
419
|
}).catch(() => {
|
|
350
|
-
|
|
351
|
-
|
|
420
|
+
newProject.isFirstRunComplete = true;
|
|
421
|
+
newProject.isFirstRunSuccessful = false;
|
|
352
422
|
}).then(() => {
|
|
353
423
|
//if we found a deprecated brsconfig.json, add a diagnostic warning the user
|
|
354
424
|
if (configFilePath && path.basename(configFilePath) === 'brsconfig.json') {
|
|
@@ -357,13 +427,10 @@ class LanguageServer {
|
|
|
357
427
|
}
|
|
358
428
|
});
|
|
359
429
|
}
|
|
360
|
-
|
|
361
|
-
* @param srcPath The absolute path to the source file on disk
|
|
362
|
-
*/
|
|
363
|
-
async createStandaloneFileWorkspace(srcPath) {
|
|
430
|
+
async createStandaloneFileProject(srcPath) {
|
|
364
431
|
//skip this workspace if we already have it
|
|
365
|
-
if (this.
|
|
366
|
-
return this.
|
|
432
|
+
if (this.standaloneFileProjects[srcPath]) {
|
|
433
|
+
return this.standaloneFileProjects[srcPath];
|
|
367
434
|
}
|
|
368
435
|
let builder = new ProgramBuilder_1.ProgramBuilder();
|
|
369
436
|
//prevent clearing the console on run...this isn't the CLI so we want to keep a full log of everything
|
|
@@ -390,45 +457,46 @@ class LanguageServer {
|
|
|
390
457
|
] })).catch((err) => {
|
|
391
458
|
console.error(err);
|
|
392
459
|
});
|
|
393
|
-
let
|
|
460
|
+
let newProject = {
|
|
394
461
|
builder: builder,
|
|
395
462
|
firstRunPromise: firstRunPromise,
|
|
463
|
+
projectPath: srcPath,
|
|
396
464
|
workspacePath: srcPath,
|
|
397
465
|
isFirstRunComplete: false,
|
|
398
466
|
isFirstRunSuccessful: false,
|
|
399
467
|
configFilePath: configFilePath,
|
|
400
|
-
|
|
468
|
+
isStandaloneFileProject: true
|
|
401
469
|
};
|
|
402
|
-
this.
|
|
470
|
+
this.standaloneFileProjects[srcPath] = newProject;
|
|
403
471
|
await firstRunPromise.then(() => {
|
|
404
|
-
|
|
405
|
-
|
|
472
|
+
newProject.isFirstRunComplete = true;
|
|
473
|
+
newProject.isFirstRunSuccessful = true;
|
|
406
474
|
}).catch(() => {
|
|
407
|
-
|
|
408
|
-
|
|
475
|
+
newProject.isFirstRunComplete = true;
|
|
476
|
+
newProject.isFirstRunSuccessful = false;
|
|
409
477
|
});
|
|
410
|
-
return
|
|
478
|
+
return newProject;
|
|
411
479
|
}
|
|
412
|
-
|
|
413
|
-
let
|
|
414
|
-
for (let key in this.
|
|
415
|
-
|
|
480
|
+
getProjects() {
|
|
481
|
+
let projects = this.projects.slice();
|
|
482
|
+
for (let key in this.standaloneFileProjects) {
|
|
483
|
+
projects.push(this.standaloneFileProjects[key]);
|
|
416
484
|
}
|
|
417
|
-
return
|
|
485
|
+
return projects;
|
|
418
486
|
}
|
|
419
487
|
/**
|
|
420
488
|
* Provide a list of completion items based on the current cursor position
|
|
421
489
|
* @param textDocumentPosition
|
|
422
490
|
*/
|
|
423
|
-
async onCompletion(
|
|
491
|
+
async onCompletion(params) {
|
|
424
492
|
//ensure programs are initialized
|
|
425
|
-
await this.
|
|
426
|
-
let filePath = util_1.util.uriToPath(uri);
|
|
493
|
+
await this.waitAllProjectFirstRuns();
|
|
494
|
+
let filePath = util_1.util.uriToPath(params.textDocument.uri);
|
|
427
495
|
//wait until the file has settled
|
|
428
496
|
await this.keyedThrottler.onIdleOnce(filePath, true);
|
|
429
497
|
let completions = this
|
|
430
|
-
.
|
|
431
|
-
.flatMap(workspace => workspace.builder.program.getCompletions(filePath, position));
|
|
498
|
+
.getProjects()
|
|
499
|
+
.flatMap(workspace => workspace.builder.program.getCompletions(filePath, params.position));
|
|
432
500
|
for (let completion of completions) {
|
|
433
501
|
completion.commitCharacters = ['.'];
|
|
434
502
|
}
|
|
@@ -451,12 +519,12 @@ class LanguageServer {
|
|
|
451
519
|
}
|
|
452
520
|
async onCodeAction(params) {
|
|
453
521
|
//ensure programs are initialized
|
|
454
|
-
await this.
|
|
522
|
+
await this.waitAllProjectFirstRuns();
|
|
455
523
|
let srcPath = util_1.util.uriToPath(params.textDocument.uri);
|
|
456
524
|
//wait until the file has settled
|
|
457
525
|
await this.keyedThrottler.onIdleOnce(srcPath, true);
|
|
458
526
|
const codeActions = this
|
|
459
|
-
.
|
|
527
|
+
.getProjects()
|
|
460
528
|
//skip programs that don't have this file
|
|
461
529
|
.filter(x => { var _a, _b; return (_b = (_a = x.builder) === null || _a === void 0 ? void 0 : _a.program) === null || _b === void 0 ? void 0 : _b.hasFile(srcPath); })
|
|
462
530
|
.flatMap(workspace => workspace.builder.program.getCodeActions(srcPath, params.range));
|
|
@@ -469,38 +537,42 @@ class LanguageServer {
|
|
|
469
537
|
return codeActions;
|
|
470
538
|
}
|
|
471
539
|
/**
|
|
472
|
-
*
|
|
540
|
+
* Remove a project from the language server
|
|
473
541
|
*/
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
542
|
+
removeProject(project) {
|
|
543
|
+
var _a;
|
|
544
|
+
const idx = this.projects.indexOf(project);
|
|
545
|
+
if (idx > -1) {
|
|
546
|
+
this.projects.splice(idx, 1);
|
|
547
|
+
}
|
|
548
|
+
(_a = project === null || project === void 0 ? void 0 : project.builder) === null || _a === void 0 ? void 0 : _a.dispose();
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Reload each of the specified workspaces
|
|
552
|
+
*/
|
|
553
|
+
async reloadProjects(projects) {
|
|
554
|
+
await Promise.all(projects.map(async (project) => {
|
|
477
555
|
//ensure the workspace has finished starting up
|
|
478
556
|
try {
|
|
479
|
-
await
|
|
557
|
+
await project.firstRunPromise;
|
|
480
558
|
}
|
|
481
559
|
catch (e) { }
|
|
482
560
|
//handle standard workspace
|
|
483
|
-
if (
|
|
484
|
-
|
|
485
|
-
if (idx > -1) {
|
|
486
|
-
//remove this workspace
|
|
487
|
-
this.workspaces.splice(idx, 1);
|
|
488
|
-
//dispose this workspace's resources
|
|
489
|
-
workspace.builder.dispose();
|
|
490
|
-
}
|
|
561
|
+
if (project.isStandaloneFileProject === false) {
|
|
562
|
+
this.removeProject(project);
|
|
491
563
|
//create a new workspace/brs program
|
|
492
|
-
await this.
|
|
564
|
+
await this.createProject(project.projectPath, project.workspacePath);
|
|
493
565
|
//handle temp workspace
|
|
494
566
|
}
|
|
495
567
|
else {
|
|
496
|
-
|
|
497
|
-
delete this.
|
|
498
|
-
await this.
|
|
568
|
+
project.builder.dispose();
|
|
569
|
+
delete this.standaloneFileProjects[project.projectPath];
|
|
570
|
+
await this.createStandaloneFileProject(project.projectPath);
|
|
499
571
|
}
|
|
500
572
|
}));
|
|
501
|
-
if (
|
|
573
|
+
if (projects.length > 0) {
|
|
502
574
|
//wait for all of the programs to finish starting up
|
|
503
|
-
await this.
|
|
575
|
+
await this.waitAllProjectFirstRuns();
|
|
504
576
|
// valdiate all workspaces
|
|
505
577
|
this.validateAllThrottled(); //eslint-disable-line
|
|
506
578
|
}
|
|
@@ -517,42 +589,42 @@ class LanguageServer {
|
|
|
517
589
|
*
|
|
518
590
|
* Sometimes files that used to be included are now excluded, so those open files need to be re-processed as standalone
|
|
519
591
|
*/
|
|
520
|
-
async
|
|
592
|
+
async synchronizeStandaloneProjects() {
|
|
521
593
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
522
594
|
//remove standalone workspaces that are now included in projects
|
|
523
|
-
for (let standaloneFilePath in this.
|
|
524
|
-
let
|
|
525
|
-
for (let
|
|
526
|
-
await
|
|
527
|
-
let dest = rokuDeploy.getDestPath(standaloneFilePath, (_d = (_c = (_b = (_a =
|
|
595
|
+
for (let standaloneFilePath in this.standaloneFileProjects) {
|
|
596
|
+
let standaloneProject = this.standaloneFileProjects[standaloneFilePath];
|
|
597
|
+
for (let project of this.projects) {
|
|
598
|
+
await standaloneProject.firstRunPromise;
|
|
599
|
+
let dest = roku_deploy_1.rokuDeploy.getDestPath(standaloneFilePath, (_d = (_c = (_b = (_a = project === null || project === void 0 ? void 0 : project.builder) === null || _a === void 0 ? void 0 : _a.program) === null || _b === void 0 ? void 0 : _b.options) === null || _c === void 0 ? void 0 : _c.files) !== null && _d !== void 0 ? _d : [], this.getRootDir(project));
|
|
528
600
|
//destroy this standalone workspace because the file has now been included in an actual workspace,
|
|
529
601
|
//or if the workspace wants the file
|
|
530
|
-
if (((_f = (_e =
|
|
531
|
-
|
|
532
|
-
delete this.
|
|
602
|
+
if (((_f = (_e = project === null || project === void 0 ? void 0 : project.builder) === null || _e === void 0 ? void 0 : _e.program) === null || _f === void 0 ? void 0 : _f.hasFile(standaloneFilePath)) || dest) {
|
|
603
|
+
standaloneProject.builder.dispose();
|
|
604
|
+
delete this.standaloneFileProjects[standaloneFilePath];
|
|
533
605
|
}
|
|
534
606
|
}
|
|
535
607
|
}
|
|
536
|
-
//create standalone
|
|
608
|
+
//create standalone projects for open files that no longer have a project
|
|
537
609
|
let textDocuments = this.documents.all();
|
|
538
610
|
outer: for (let textDocument of textDocuments) {
|
|
539
611
|
let filePath = vscode_uri_1.URI.parse(textDocument.uri).fsPath;
|
|
540
|
-
let
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
if (((_m = (_l = workspace === null || workspace === void 0 ? void 0 : workspace.builder) === null || _l === void 0 ? void 0 : _l.program) === null || _m === void 0 ? void 0 : _m.hasFile(filePath)) || dest) {
|
|
612
|
+
for (let project of this.getProjects()) {
|
|
613
|
+
let dest = roku_deploy_1.rokuDeploy.getDestPath(filePath, (_k = (_j = (_h = (_g = project === null || project === void 0 ? void 0 : project.builder) === null || _g === void 0 ? void 0 : _g.program) === null || _h === void 0 ? void 0 : _h.options) === null || _j === void 0 ? void 0 : _j.files) !== null && _k !== void 0 ? _k : [], this.getRootDir(project));
|
|
614
|
+
//if this project has the file, or it wants the file, do NOT make a standaloneProject for this file
|
|
615
|
+
if (((_m = (_l = project === null || project === void 0 ? void 0 : project.builder) === null || _l === void 0 ? void 0 : _l.program) === null || _m === void 0 ? void 0 : _m.hasFile(filePath)) || dest) {
|
|
545
616
|
continue outer;
|
|
546
617
|
}
|
|
547
618
|
}
|
|
548
619
|
//if we got here, no workspace has this file, so make a standalone file workspace
|
|
549
|
-
let
|
|
550
|
-
await
|
|
620
|
+
let project = await this.createStandaloneFileProject(filePath);
|
|
621
|
+
await project.firstRunPromise;
|
|
551
622
|
}
|
|
552
623
|
}
|
|
553
624
|
async onDidChangeConfiguration() {
|
|
554
625
|
if (this.hasConfigurationCapability) {
|
|
555
|
-
|
|
626
|
+
//if the user changes any config value, just mass-reload all projects
|
|
627
|
+
await this.reloadProjects(this.getProjects());
|
|
556
628
|
// Reset all cached document settings
|
|
557
629
|
}
|
|
558
630
|
else {
|
|
@@ -570,9 +642,9 @@ class LanguageServer {
|
|
|
570
642
|
*/
|
|
571
643
|
async onDidChangeWatchedFiles(params) {
|
|
572
644
|
//ensure programs are initialized
|
|
573
|
-
await this.
|
|
645
|
+
await this.waitAllProjectFirstRuns();
|
|
574
646
|
this.connection.sendNotification('build-status', 'building');
|
|
575
|
-
let
|
|
647
|
+
let projects = this.getProjects();
|
|
576
648
|
//convert all file paths to absolute paths
|
|
577
649
|
let changes = params.changes.map(x => {
|
|
578
650
|
return {
|
|
@@ -585,23 +657,27 @@ class LanguageServer {
|
|
|
585
657
|
changes = changes.filter(x => keys.includes(x.srcPath));
|
|
586
658
|
//if we have changes to work with
|
|
587
659
|
if (changes.length > 0) {
|
|
660
|
+
//if any bsconfig files were added or deleted, re-sync all projects instead of the more specific approach below
|
|
661
|
+
if (changes.find(x => (x.type === node_1.FileChangeType.Created || x.type === node_1.FileChangeType.Deleted) && path.basename(x.srcPath).toLowerCase() === 'bsconfig.json')) {
|
|
662
|
+
return this.syncProjects();
|
|
663
|
+
}
|
|
588
664
|
//reload any workspace whose bsconfig.json file has changed
|
|
589
665
|
{
|
|
590
|
-
let
|
|
666
|
+
let projectsToReload = [];
|
|
591
667
|
//get the file paths as a string array
|
|
592
668
|
let filePaths = changes.map((x) => x.srcPath);
|
|
593
|
-
for (let
|
|
594
|
-
if (
|
|
595
|
-
|
|
669
|
+
for (let project of projects) {
|
|
670
|
+
if (project.configFilePath && filePaths.includes(project.configFilePath)) {
|
|
671
|
+
projectsToReload.push(project);
|
|
596
672
|
}
|
|
597
673
|
}
|
|
598
|
-
if (
|
|
599
|
-
//vsc can generate a ton of these changes, for vsc system files, so we need to bail if there's no work to do on any of our actual
|
|
600
|
-
//reload any
|
|
601
|
-
await this.
|
|
674
|
+
if (projectsToReload.length > 0) {
|
|
675
|
+
//vsc can generate a ton of these changes, for vsc system files, so we need to bail if there's no work to do on any of our actual project files
|
|
676
|
+
//reload any projects that need to be reloaded
|
|
677
|
+
await this.reloadProjects(projectsToReload);
|
|
602
678
|
}
|
|
603
|
-
//
|
|
604
|
-
|
|
679
|
+
//reassign `projects` to the non-reloaded projects
|
|
680
|
+
projects = projects.filter(x => !projectsToReload.includes(x));
|
|
605
681
|
}
|
|
606
682
|
//convert created folders into a list of files of their contents
|
|
607
683
|
const directoryChanges = changes
|
|
@@ -619,10 +695,10 @@ class LanguageServer {
|
|
|
619
695
|
.filter(dirPath => !dirPath.includes('.roku-deploy-staging'))
|
|
620
696
|
//get the files for each folder recursively
|
|
621
697
|
.flatMap(dirPath => {
|
|
622
|
-
//
|
|
623
|
-
let
|
|
624
|
-
|
|
625
|
-
|
|
698
|
+
//look up all files
|
|
699
|
+
let files = fastGlob.sync('**/*', {
|
|
700
|
+
absolute: true,
|
|
701
|
+
cwd: roku_deploy_1.util.toForwardSlashes(dirPath)
|
|
626
702
|
});
|
|
627
703
|
return files.map(x => {
|
|
628
704
|
return {
|
|
@@ -634,7 +710,7 @@ class LanguageServer {
|
|
|
634
710
|
//add the new file changes to the changes array.
|
|
635
711
|
changes.push(...newFileChanges);
|
|
636
712
|
//give every workspace the chance to handle file changes
|
|
637
|
-
await Promise.all(
|
|
713
|
+
await Promise.all(projects.map((project) => this.handleFileChanges(project, changes)));
|
|
638
714
|
}
|
|
639
715
|
this.connection.sendNotification('build-status', 'success');
|
|
640
716
|
}
|
|
@@ -643,13 +719,13 @@ class LanguageServer {
|
|
|
643
719
|
* any file changes you receive with no unexpected side-effects
|
|
644
720
|
* @param changes
|
|
645
721
|
*/
|
|
646
|
-
async handleFileChanges(
|
|
722
|
+
async handleFileChanges(project, changes) {
|
|
647
723
|
//this loop assumes paths are both file paths and folder paths, which eliminates the need to detect.
|
|
648
724
|
//All functions below can handle being given a file path AND a folder path, and will only operate on the one they are looking for
|
|
649
725
|
let consumeCount = 0;
|
|
650
726
|
await Promise.all(changes.map(async (change) => {
|
|
651
727
|
await this.keyedThrottler.run(change.srcPath, async () => {
|
|
652
|
-
consumeCount += await this.handleFileChange(
|
|
728
|
+
consumeCount += await this.handleFileChange(project, change) ? 1 : 0;
|
|
653
729
|
});
|
|
654
730
|
}));
|
|
655
731
|
if (consumeCount > 0) {
|
|
@@ -661,14 +737,12 @@ class LanguageServer {
|
|
|
661
737
|
* any file changes you receive with no unexpected side-effects
|
|
662
738
|
* @param changes
|
|
663
739
|
*/
|
|
664
|
-
async handleFileChange(
|
|
665
|
-
const program =
|
|
666
|
-
const options = workspace.builder.options;
|
|
667
|
-
const rootDir = workspace.builder.rootDir;
|
|
740
|
+
async handleFileChange(project, change) {
|
|
741
|
+
const { program, options, rootDir } = project.builder;
|
|
668
742
|
//deleted
|
|
669
743
|
if (change.type === node_1.FileChangeType.Deleted) {
|
|
670
744
|
//try to act on this path as a directory
|
|
671
|
-
|
|
745
|
+
project.builder.program.removeFilesInFolder(change.srcPath);
|
|
672
746
|
//if this is a file loaded in the program, remove it
|
|
673
747
|
if (program.hasFile(change.srcPath)) {
|
|
674
748
|
program.removeFile(change.srcPath);
|
|
@@ -682,13 +756,13 @@ class LanguageServer {
|
|
|
682
756
|
else if (change.type === node_1.FileChangeType.Created) {
|
|
683
757
|
// thanks to `onDidChangeWatchedFiles`, we can safely assume that all "Created" changes are file paths, (not directories)
|
|
684
758
|
//get the dest path for this file.
|
|
685
|
-
let destPath = rokuDeploy.getDestPath(change.srcPath, options.files, rootDir);
|
|
759
|
+
let destPath = roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir);
|
|
686
760
|
//if we got a dest path, then the program wants this file
|
|
687
761
|
if (destPath) {
|
|
688
762
|
program.setFile({
|
|
689
763
|
src: change.srcPath,
|
|
690
|
-
dest: rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
|
|
691
|
-
}, await
|
|
764
|
+
dest: roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
|
|
765
|
+
}, await project.builder.getFileContents(change.srcPath));
|
|
692
766
|
return true;
|
|
693
767
|
}
|
|
694
768
|
else {
|
|
@@ -703,8 +777,8 @@ class LanguageServer {
|
|
|
703
777
|
if (await util_1.util.pathExists(change.srcPath)) {
|
|
704
778
|
program.setFile({
|
|
705
779
|
src: change.srcPath,
|
|
706
|
-
dest: rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
|
|
707
|
-
}, await
|
|
780
|
+
dest: roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
|
|
781
|
+
}, await project.builder.getFileContents(change.srcPath));
|
|
708
782
|
}
|
|
709
783
|
else {
|
|
710
784
|
program.removeFile(change.srcPath);
|
|
@@ -714,75 +788,76 @@ class LanguageServer {
|
|
|
714
788
|
}
|
|
715
789
|
async onHover(params) {
|
|
716
790
|
//ensure programs are initialized
|
|
717
|
-
await this.
|
|
718
|
-
|
|
719
|
-
let
|
|
720
|
-
let hovers = await Promise.all(Array.prototype.concat.call([],
|
|
791
|
+
await this.waitAllProjectFirstRuns();
|
|
792
|
+
const srcPath = util_1.util.uriToPath(params.textDocument.uri);
|
|
793
|
+
let projects = this.getProjects();
|
|
794
|
+
let hovers = await Promise.all(Array.prototype.concat.call([], projects.map(async (x) => x.builder.program.getHover(srcPath, params.position))));
|
|
721
795
|
//return the first non-falsey hover. TODO is there a way to handle multiple hover results?
|
|
722
796
|
let hover = hovers.filter((x) => !!x)[0];
|
|
723
797
|
return hover;
|
|
724
798
|
}
|
|
725
|
-
async onDocumentClose(
|
|
726
|
-
|
|
727
|
-
let
|
|
799
|
+
async onDocumentClose(event) {
|
|
800
|
+
const { document } = event;
|
|
801
|
+
let filePath = vscode_uri_1.URI.parse(document.uri).fsPath;
|
|
802
|
+
let standaloneFileProject = this.standaloneFileProjects[filePath];
|
|
728
803
|
//if this was a temp file, close it
|
|
729
|
-
if (
|
|
730
|
-
await
|
|
731
|
-
|
|
732
|
-
delete this.
|
|
804
|
+
if (standaloneFileProject) {
|
|
805
|
+
await standaloneFileProject.firstRunPromise;
|
|
806
|
+
standaloneFileProject.builder.dispose();
|
|
807
|
+
delete this.standaloneFileProjects[filePath];
|
|
733
808
|
await this.sendDiagnostics();
|
|
734
809
|
}
|
|
735
810
|
}
|
|
736
|
-
async validateTextDocument(
|
|
811
|
+
async validateTextDocument(event) {
|
|
812
|
+
const { document } = event;
|
|
737
813
|
//ensure programs are initialized
|
|
738
|
-
await this.
|
|
739
|
-
let filePath = vscode_uri_1.URI.parse(
|
|
814
|
+
await this.waitAllProjectFirstRuns();
|
|
815
|
+
let filePath = vscode_uri_1.URI.parse(document.uri).fsPath;
|
|
740
816
|
try {
|
|
741
817
|
//throttle file processing. first call is run immediately, and then the last call is processed.
|
|
742
818
|
await this.keyedThrottler.run(filePath, () => {
|
|
743
819
|
var _a;
|
|
744
820
|
this.connection.sendNotification('build-status', 'building');
|
|
745
|
-
let documentText =
|
|
746
|
-
for (const
|
|
821
|
+
let documentText = document.getText();
|
|
822
|
+
for (const project of this.getProjects()) {
|
|
747
823
|
//only add or replace existing files. All of the files in the project should
|
|
748
824
|
//have already been loaded by other means
|
|
749
|
-
if (
|
|
750
|
-
let rootDir = (_a =
|
|
751
|
-
let dest = rokuDeploy.getDestPath(filePath,
|
|
752
|
-
|
|
825
|
+
if (project.builder.program.hasFile(filePath)) {
|
|
826
|
+
let rootDir = (_a = project.builder.program.options.rootDir) !== null && _a !== void 0 ? _a : project.builder.program.options.cwd;
|
|
827
|
+
let dest = roku_deploy_1.rokuDeploy.getDestPath(filePath, project.builder.program.options.files, rootDir);
|
|
828
|
+
project.builder.program.setFile({
|
|
753
829
|
src: filePath,
|
|
754
830
|
dest: dest
|
|
755
831
|
}, documentText);
|
|
756
832
|
}
|
|
757
833
|
}
|
|
758
834
|
});
|
|
759
|
-
// validate all
|
|
835
|
+
// validate all projects
|
|
760
836
|
await this.validateAllThrottled();
|
|
761
837
|
}
|
|
762
838
|
catch (e) {
|
|
763
|
-
this.sendCriticalFailure(`Critical error parsing/ validating ${filePath}: ${e.message}`);
|
|
839
|
+
this.sendCriticalFailure(`Critical error parsing / validating ${filePath}: ${e.message}`);
|
|
764
840
|
}
|
|
765
841
|
}
|
|
766
842
|
async validateAll() {
|
|
767
843
|
var _a;
|
|
768
844
|
try {
|
|
769
845
|
//synchronize parsing for open files that were included/excluded from projects
|
|
770
|
-
await this.
|
|
771
|
-
let
|
|
846
|
+
await this.synchronizeStandaloneProjects();
|
|
847
|
+
let projects = this.getProjects();
|
|
772
848
|
//validate all programs
|
|
773
|
-
await Promise.all(
|
|
774
|
-
await this.sendDiagnostics();
|
|
849
|
+
await Promise.all(projects.map((x) => x.builder.program.validate()));
|
|
775
850
|
}
|
|
776
851
|
catch (e) {
|
|
777
852
|
this.connection.console.error(e);
|
|
778
|
-
this.sendCriticalFailure(`Critical error validating
|
|
853
|
+
this.sendCriticalFailure(`Critical error validating project: ${e.message}${(_a = e.stack) !== null && _a !== void 0 ? _a : ''}`);
|
|
779
854
|
}
|
|
780
855
|
this.connection.sendNotification('build-status', 'success');
|
|
781
856
|
}
|
|
782
857
|
async onWorkspaceSymbol(params) {
|
|
783
|
-
await this.
|
|
784
|
-
const results = util_1.util.flatMap(await Promise.all(this.
|
|
785
|
-
return
|
|
858
|
+
await this.waitAllProjectFirstRuns();
|
|
859
|
+
const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
|
|
860
|
+
return project.builder.program.getWorkspaceSymbols();
|
|
786
861
|
})), c => c);
|
|
787
862
|
// Remove duplicates
|
|
788
863
|
const allSymbols = Object.values(results.reduce((map, symbol) => {
|
|
@@ -793,31 +868,31 @@ class LanguageServer {
|
|
|
793
868
|
return allSymbols;
|
|
794
869
|
}
|
|
795
870
|
async onDocumentSymbol(params) {
|
|
796
|
-
await this.
|
|
871
|
+
await this.waitAllProjectFirstRuns();
|
|
797
872
|
await this.keyedThrottler.onIdleOnce(util_1.util.uriToPath(params.textDocument.uri), true);
|
|
798
873
|
const srcPath = util_1.util.uriToPath(params.textDocument.uri);
|
|
799
|
-
for (const
|
|
800
|
-
const file =
|
|
874
|
+
for (const project of this.getProjects()) {
|
|
875
|
+
const file = project.builder.program.getFile(srcPath);
|
|
801
876
|
if ((0, reflection_1.isBrsFile)(file)) {
|
|
802
877
|
return file.getDocumentSymbols();
|
|
803
878
|
}
|
|
804
879
|
}
|
|
805
880
|
}
|
|
806
881
|
async onDefinition(params) {
|
|
807
|
-
await this.
|
|
882
|
+
await this.waitAllProjectFirstRuns();
|
|
808
883
|
const srcPath = util_1.util.uriToPath(params.textDocument.uri);
|
|
809
|
-
const results = util_1.util.flatMap(await Promise.all(this.
|
|
810
|
-
return
|
|
884
|
+
const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
|
|
885
|
+
return project.builder.program.getDefinition(srcPath, params.position);
|
|
811
886
|
})), c => c);
|
|
812
887
|
return results;
|
|
813
888
|
}
|
|
814
889
|
async onSignatureHelp(params) {
|
|
815
890
|
var _a, _b, _c;
|
|
816
|
-
await this.
|
|
891
|
+
await this.waitAllProjectFirstRuns();
|
|
817
892
|
const filepath = util_1.util.uriToPath(params.textDocument.uri);
|
|
818
893
|
await this.keyedThrottler.onIdleOnce(filepath, true);
|
|
819
894
|
try {
|
|
820
|
-
const signatures = util_1.util.flatMap(await Promise.all(this.
|
|
895
|
+
const signatures = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => project.builder.program.getSignatureHelp(filepath, params.position))), c => c);
|
|
821
896
|
const activeSignature = signatures.length > 0 ? 0 : null;
|
|
822
897
|
const activeParameter = activeSignature >= 0 ? (_a = signatures[activeSignature]) === null || _a === void 0 ? void 0 : _a.index : null;
|
|
823
898
|
let results = {
|
|
@@ -837,22 +912,22 @@ class LanguageServer {
|
|
|
837
912
|
}
|
|
838
913
|
}
|
|
839
914
|
async onReferences(params) {
|
|
840
|
-
await this.
|
|
915
|
+
await this.waitAllProjectFirstRuns();
|
|
841
916
|
const position = params.position;
|
|
842
917
|
const srcPath = util_1.util.uriToPath(params.textDocument.uri);
|
|
843
|
-
const results = util_1.util.flatMap(await Promise.all(this.
|
|
844
|
-
return
|
|
918
|
+
const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
|
|
919
|
+
return project.builder.program.getReferences(srcPath, position);
|
|
845
920
|
})), c => c);
|
|
846
921
|
return results.filter((r) => r);
|
|
847
922
|
}
|
|
848
923
|
async onFullSemanticTokens(params) {
|
|
849
|
-
await this.
|
|
924
|
+
await this.waitAllProjectFirstRuns();
|
|
850
925
|
await this.keyedThrottler.onIdleOnce(util_1.util.uriToPath(params.textDocument.uri), true);
|
|
851
926
|
const srcPath = util_1.util.uriToPath(params.textDocument.uri);
|
|
852
|
-
for (const
|
|
927
|
+
for (const project of this.projects) {
|
|
853
928
|
//find the first program that has this file, since it would be incredibly inefficient to generate semantic tokens for the same file multiple times.
|
|
854
|
-
if (
|
|
855
|
-
let semanticTokens =
|
|
929
|
+
if (project.builder.program.hasFile(srcPath)) {
|
|
930
|
+
let semanticTokens = project.builder.program.getSemanticTokens(srcPath);
|
|
856
931
|
return {
|
|
857
932
|
data: (0, SemanticTokenUtils_1.encodeSemanticTokens)(semanticTokens)
|
|
858
933
|
};
|
|
@@ -860,20 +935,27 @@ class LanguageServer {
|
|
|
860
935
|
}
|
|
861
936
|
}
|
|
862
937
|
async sendDiagnostics() {
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
this.
|
|
868
|
-
|
|
869
|
-
diagnostics
|
|
870
|
-
|
|
871
|
-
|
|
938
|
+
await this.sendDiagnosticsThrottler.run(async () => {
|
|
939
|
+
//wait for all programs to finish running. This ensures the `Program` exists.
|
|
940
|
+
await Promise.all(this.projects.map(x => x.firstRunPromise));
|
|
941
|
+
//Get only the changes to diagnostics since the last time we sent them to the client
|
|
942
|
+
const patch = this.diagnosticCollection.getPatch(this.projects);
|
|
943
|
+
for (let filePath in patch) {
|
|
944
|
+
const diagnostics = patch[filePath].map(d => util_1.util.toDiagnostic(d));
|
|
945
|
+
this.connection.sendDiagnostics({
|
|
946
|
+
uri: vscode_uri_1.URI.file(filePath).toString(),
|
|
947
|
+
diagnostics: diagnostics
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
});
|
|
872
951
|
}
|
|
873
952
|
async onExecuteCommand(params) {
|
|
874
|
-
await this.
|
|
953
|
+
await this.waitAllProjectFirstRuns();
|
|
875
954
|
if (params.command === CustomCommands.TranspileFile) {
|
|
876
|
-
|
|
955
|
+
const result = await this.transpileFile(params.arguments[0]);
|
|
956
|
+
//back-compat: include `pathAbsolute` property so older vscode versions still work
|
|
957
|
+
result.pathAbsolute = result.srcPath;
|
|
958
|
+
return result;
|
|
877
959
|
}
|
|
878
960
|
}
|
|
879
961
|
/**
|
|
@@ -881,12 +963,11 @@ class LanguageServer {
|
|
|
881
963
|
*/
|
|
882
964
|
async transpileFile(srcPath) {
|
|
883
965
|
//wait all program first runs
|
|
884
|
-
await this.
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
return workspace.builder.program.getTranspiledFileContents(srcPath);
|
|
966
|
+
await this.waitAllProjectFirstRuns();
|
|
967
|
+
//find the first project that has this file
|
|
968
|
+
for (let project of this.getProjects()) {
|
|
969
|
+
if (project.builder.program.hasFile(srcPath)) {
|
|
970
|
+
return project.builder.program.getTranspiledFileContents(srcPath);
|
|
890
971
|
}
|
|
891
972
|
}
|
|
892
973
|
}
|