brighterscript 1.0.0-alpha.44 → 1.0.0-alpha.45
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 +63 -0
- package/bsconfig.schema.json +6 -1
- package/dist/AstValidationSegmenter.js +6 -1
- package/dist/AstValidationSegmenter.js.map +1 -1
- package/dist/BsConfig.d.ts +4 -0
- package/dist/BusyStatusTracker.d.ts +37 -7
- package/dist/BusyStatusTracker.js +73 -8
- package/dist/BusyStatusTracker.js.map +1 -1
- package/dist/Cache.d.ts +0 -4
- package/dist/Cache.js +0 -6
- package/dist/Cache.js.map +1 -1
- package/dist/CrossScopeValidator.d.ts +1 -1
- package/dist/CrossScopeValidator.js +4 -4
- package/dist/CrossScopeValidator.js.map +1 -1
- package/dist/DiagnosticCollection.d.ts +19 -5
- package/dist/DiagnosticCollection.js +71 -23
- package/dist/DiagnosticCollection.js.map +1 -1
- package/dist/DiagnosticFilterer.d.ts +14 -1
- package/dist/DiagnosticFilterer.js +130 -12
- package/dist/DiagnosticFilterer.js.map +1 -1
- package/dist/DiagnosticManager.d.ts +11 -1
- package/dist/DiagnosticManager.js +192 -35
- package/dist/DiagnosticManager.js.map +1 -1
- package/dist/LanguageServer.d.ts +82 -139
- package/dist/LanguageServer.js +402 -980
- package/dist/LanguageServer.js.map +1 -1
- package/dist/Logger.d.ts +9 -4
- package/dist/Logger.js +30 -6
- package/dist/Logger.js.map +1 -1
- package/dist/PluginInterface.d.ts +8 -8
- package/dist/PluginInterface.js.map +1 -1
- package/dist/Program.d.ts +23 -4
- package/dist/Program.js +294 -194
- package/dist/Program.js.map +1 -1
- package/dist/ProgramBuilder.d.ts +22 -7
- package/dist/ProgramBuilder.js +44 -21
- package/dist/ProgramBuilder.js.map +1 -1
- package/dist/Scope.d.ts +11 -6
- package/dist/Scope.js +60 -36
- package/dist/Scope.js.map +1 -1
- package/dist/SemanticTokenUtils.js +1 -1
- package/dist/SemanticTokenUtils.js.map +1 -1
- package/dist/astUtils/reflection.d.ts +4 -4
- package/dist/astUtils/reflection.js +12 -10
- package/dist/astUtils/reflection.js.map +1 -1
- package/dist/bscPlugin/BscPlugin.d.ts +3 -3
- package/dist/bscPlugin/BscPlugin.js.map +1 -1
- package/dist/bscPlugin/CallExpressionInfo.js +4 -2
- package/dist/bscPlugin/CallExpressionInfo.js.map +1 -1
- package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +1 -1
- package/dist/bscPlugin/completions/CompletionsProcessor.js +15 -15
- package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
- package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +82 -5
- package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -1
- package/dist/bscPlugin/hover/HoverProcessor.js +3 -0
- package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
- package/dist/bscPlugin/validation/ScopeValidator.d.ts +4 -1
- package/dist/bscPlugin/validation/ScopeValidator.js +161 -61
- package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
- package/dist/common/Sequencer.d.ts +53 -0
- package/dist/common/Sequencer.js +232 -0
- package/dist/common/Sequencer.js.map +1 -0
- package/dist/common/Sequencer.spec.d.ts +1 -0
- package/dist/common/Sequencer.spec.js +75 -0
- package/dist/common/Sequencer.spec.js.map +1 -0
- package/dist/deferred.d.ts +2 -0
- package/dist/deferred.js +10 -0
- package/dist/deferred.js.map +1 -1
- package/dist/examples/plugins/removePrint.d.ts +2 -2
- package/dist/examples/plugins/removePrint.js.map +1 -1
- package/dist/files/BrsFile.d.ts +1 -1
- package/dist/files/BrsFile.js +11 -40
- package/dist/files/BrsFile.js.map +1 -1
- package/dist/files/BrsFile.spec.js +51 -2
- package/dist/files/BrsFile.spec.js.map +1 -1
- package/dist/files/BscFile.d.ts +1 -0
- package/dist/files/LazyFileData.d.ts +1 -0
- package/dist/files/XmlFile.spec.js +1 -1
- package/dist/files/XmlFile.spec.js.map +1 -1
- package/dist/globalCallables.js +186 -186
- package/dist/globalCallables.js.map +1 -1
- package/dist/interfaces.d.ts +56 -13
- package/dist/interfaces.js.map +1 -1
- package/dist/lexer/Lexer.js +1 -1
- package/dist/lexer/Lexer.js.map +1 -1
- package/dist/logging.d.ts +6 -1
- package/dist/logging.js +14 -1
- package/dist/logging.js.map +1 -1
- package/dist/lsp/ActionQueue.d.ts +35 -0
- package/dist/lsp/ActionQueue.js +115 -0
- package/dist/lsp/ActionQueue.js.map +1 -0
- package/dist/lsp/ActionQueue.spec.d.ts +1 -0
- package/dist/lsp/ActionQueue.spec.js +80 -0
- package/dist/lsp/ActionQueue.spec.js.map +1 -0
- package/dist/lsp/DocumentManager.d.ts +63 -0
- package/dist/lsp/DocumentManager.js +122 -0
- package/dist/lsp/DocumentManager.js.map +1 -0
- package/dist/lsp/DocumentManager.spec.d.ts +1 -0
- package/dist/lsp/DocumentManager.spec.js +103 -0
- package/dist/lsp/DocumentManager.spec.js.map +1 -0
- package/dist/lsp/LspProject.d.ts +231 -0
- package/dist/lsp/LspProject.js +3 -0
- package/dist/lsp/LspProject.js.map +1 -0
- package/dist/lsp/PathFilterer.d.ts +75 -0
- package/dist/lsp/PathFilterer.js +196 -0
- package/dist/lsp/PathFilterer.js.map +1 -0
- package/dist/lsp/PathFilterer.spec.d.ts +1 -0
- package/dist/lsp/PathFilterer.spec.js +182 -0
- package/dist/lsp/PathFilterer.spec.js.map +1 -0
- package/dist/lsp/Project.d.ts +167 -0
- package/dist/lsp/Project.js +432 -0
- package/dist/lsp/Project.js.map +1 -0
- package/dist/lsp/Project.spec.d.ts +1 -0
- package/dist/lsp/Project.spec.js +220 -0
- package/dist/lsp/Project.spec.js.map +1 -0
- package/dist/lsp/ProjectManager.d.ts +221 -0
- package/dist/lsp/ProjectManager.js +735 -0
- package/dist/lsp/ProjectManager.js.map +1 -0
- package/dist/lsp/ProjectManager.spec.d.ts +1 -0
- package/dist/lsp/ProjectManager.spec.js +757 -0
- package/dist/lsp/ProjectManager.spec.js.map +1 -0
- package/dist/lsp/ReaderWriterManager.d.ts +21 -0
- package/dist/lsp/ReaderWriterManager.js +60 -0
- package/dist/lsp/ReaderWriterManager.js.map +1 -0
- package/dist/lsp/worker/MessageHandler.d.ts +99 -0
- package/dist/lsp/worker/MessageHandler.js +138 -0
- package/dist/lsp/worker/MessageHandler.js.map +1 -0
- package/dist/lsp/worker/MessageHandler.spec.d.ts +1 -0
- package/dist/lsp/worker/MessageHandler.spec.js +64 -0
- package/dist/lsp/worker/MessageHandler.spec.js.map +1 -0
- package/dist/lsp/worker/WorkerPool.d.ts +38 -0
- package/dist/lsp/worker/WorkerPool.js +78 -0
- package/dist/lsp/worker/WorkerPool.js.map +1 -0
- package/dist/lsp/worker/WorkerPool.spec.d.ts +1 -0
- package/dist/lsp/worker/WorkerPool.spec.js +59 -0
- package/dist/lsp/worker/WorkerPool.spec.js.map +1 -0
- package/dist/lsp/worker/WorkerThreadProject.d.ts +144 -0
- package/dist/lsp/worker/WorkerThreadProject.js +183 -0
- package/dist/lsp/worker/WorkerThreadProject.js.map +1 -0
- package/dist/lsp/worker/WorkerThreadProject.spec.d.ts +2 -0
- package/dist/lsp/worker/WorkerThreadProject.spec.js +68 -0
- package/dist/lsp/worker/WorkerThreadProject.spec.js.map +1 -0
- package/dist/lsp/worker/WorkerThreadProjectRunner.d.ts +15 -0
- package/dist/lsp/worker/WorkerThreadProjectRunner.js +58 -0
- package/dist/lsp/worker/WorkerThreadProjectRunner.js.map +1 -0
- package/dist/parser/Expression.js +7 -2
- package/dist/parser/Expression.js.map +1 -1
- package/dist/parser/Parser.spec.js +12 -0
- package/dist/parser/Parser.spec.js.map +1 -1
- package/dist/parser/Statement.js +2 -2
- package/dist/parser/Statement.js.map +1 -1
- package/dist/parser/tests/expression/TemplateStringExpression.spec.js +51 -5
- package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
- package/dist/roku-types/data.json +745 -751
- package/dist/types/BooleanType.d.ts +0 -2
- package/dist/types/BooleanType.js +4 -6
- package/dist/types/BooleanType.js.map +1 -1
- package/dist/types/BscType.js +5 -0
- package/dist/types/BscType.js.map +1 -1
- package/dist/types/BuiltInInterfaceAdder.d.ts +1 -0
- package/dist/types/BuiltInInterfaceAdder.js +24 -17
- package/dist/types/BuiltInInterfaceAdder.js.map +1 -1
- package/dist/types/DoubleType.d.ts +0 -2
- package/dist/types/DoubleType.js +4 -6
- package/dist/types/DoubleType.js.map +1 -1
- package/dist/types/DynamicType.d.ts +0 -2
- package/dist/types/DynamicType.js +3 -5
- package/dist/types/DynamicType.js.map +1 -1
- package/dist/types/FloatType.d.ts +0 -2
- package/dist/types/FloatType.js +4 -6
- package/dist/types/FloatType.js.map +1 -1
- package/dist/types/FunctionType.d.ts +0 -2
- package/dist/types/FunctionType.js +5 -7
- package/dist/types/FunctionType.js.map +1 -1
- package/dist/types/IntegerType.d.ts +0 -2
- package/dist/types/IntegerType.js +4 -6
- package/dist/types/IntegerType.js.map +1 -1
- package/dist/types/LongIntegerType.d.ts +0 -2
- package/dist/types/LongIntegerType.js +4 -6
- package/dist/types/LongIntegerType.js.map +1 -1
- package/dist/types/ObjectType.d.ts +3 -3
- package/dist/types/ObjectType.js +6 -8
- package/dist/types/ObjectType.js.map +1 -1
- package/dist/types/StringType.d.ts +0 -2
- package/dist/types/StringType.js +4 -6
- package/dist/types/StringType.js.map +1 -1
- package/dist/types/VoidType.d.ts +0 -2
- package/dist/types/VoidType.js +4 -6
- package/dist/types/VoidType.js.map +1 -1
- package/dist/types/helpers.d.ts +4 -0
- package/dist/types/helpers.js +5 -1
- package/dist/types/helpers.js.map +1 -1
- package/dist/util.d.ts +44 -16
- package/dist/util.js +196 -70
- package/dist/util.js.map +1 -1
- package/dist/validators/ClassValidator.js +2 -2
- package/dist/validators/ClassValidator.js.map +1 -1
- package/package.json +15 -5
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ProjectManager = void 0;
|
|
10
|
+
const util_1 = require("../util");
|
|
11
|
+
const roku_deploy_1 = require("roku-deploy");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
const EventEmitter = require("eventemitter3");
|
|
14
|
+
const Project_1 = require("./Project");
|
|
15
|
+
const WorkerThreadProject_1 = require("./worker/WorkerThreadProject");
|
|
16
|
+
const vscode_languageserver_protocol_1 = require("vscode-languageserver-protocol");
|
|
17
|
+
const deferred_1 = require("../deferred");
|
|
18
|
+
const DocumentManager_1 = require("./DocumentManager");
|
|
19
|
+
const BusyStatusTracker_1 = require("../BusyStatusTracker");
|
|
20
|
+
const fastGlob = require("fast-glob");
|
|
21
|
+
const PathFilterer_1 = require("./PathFilterer");
|
|
22
|
+
const logging_1 = require("../logging");
|
|
23
|
+
const Cache_1 = require("../Cache");
|
|
24
|
+
const ActionQueue_1 = require("./ActionQueue");
|
|
25
|
+
const fsExtra = require("fs-extra");
|
|
26
|
+
const FileChangeTypeLookup = Object.entries(vscode_languageserver_protocol_1.FileChangeType).reduce((acc, [key, value]) => {
|
|
27
|
+
acc[value] = key;
|
|
28
|
+
acc[key] = value;
|
|
29
|
+
return acc;
|
|
30
|
+
}, {});
|
|
31
|
+
/**
|
|
32
|
+
* Manages all brighterscript projects for the language server
|
|
33
|
+
*/
|
|
34
|
+
class ProjectManager {
|
|
35
|
+
constructor(options) {
|
|
36
|
+
var _a, _b;
|
|
37
|
+
/**
|
|
38
|
+
* Collection of all projects
|
|
39
|
+
*/
|
|
40
|
+
this.projects = [];
|
|
41
|
+
/**
|
|
42
|
+
* Collection of standalone projects. These are projects that are not part of a workspace, but are instead single files.
|
|
43
|
+
* All of these are also present in the `projects` collection.
|
|
44
|
+
*/
|
|
45
|
+
this.standaloneProjects = new Map();
|
|
46
|
+
this.busyStatusTracker = new BusyStatusTracker_1.BusyStatusTracker();
|
|
47
|
+
this.firstSync = new deferred_1.Deferred();
|
|
48
|
+
this.fileChangesQueue = new ActionQueue_1.ActionQueue({
|
|
49
|
+
maxActionDuration: 45000
|
|
50
|
+
});
|
|
51
|
+
this.emitter = new EventEmitter();
|
|
52
|
+
this.logger = (_a = options === null || options === void 0 ? void 0 : options.logger) !== null && _a !== void 0 ? _a : (0, logging_1.createLogger)();
|
|
53
|
+
this.pathFilterer = (_b = options === null || options === void 0 ? void 0 : options.pathFilterer) !== null && _b !== void 0 ? _b : new PathFilterer_1.PathFilterer({ logger: options === null || options === void 0 ? void 0 : options.logger });
|
|
54
|
+
this.documentManager = new DocumentManager_1.DocumentManager({
|
|
55
|
+
delay: ProjectManager.documentManagerDelay,
|
|
56
|
+
flushHandler: (event) => {
|
|
57
|
+
return this.flushDocumentChanges(event).catch(e => console.error(e));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
this.on('validate-begin', (event) => {
|
|
61
|
+
this.busyStatusTracker.beginScopedRun(event.project, `validate-project`);
|
|
62
|
+
});
|
|
63
|
+
this.on('validate-end', (event) => {
|
|
64
|
+
void this.busyStatusTracker.endScopedRun(event.project, `validate-project`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Apply all of the queued document changes. This should only be called as a result of the documentManager flushing changes, and never called manually
|
|
69
|
+
* @param event the document changes that have occurred since the last time we applied
|
|
70
|
+
*/
|
|
71
|
+
async flushDocumentChanges(event) {
|
|
72
|
+
var _a;
|
|
73
|
+
this.logger.info(`flushDocumentChanges`, (_a = event === null || event === void 0 ? void 0 : event.actions) === null || _a === void 0 ? void 0 : _a.map(x => ({
|
|
74
|
+
type: x.type,
|
|
75
|
+
srcPath: x.srcPath,
|
|
76
|
+
allowStandaloneProject: x.allowStandaloneProject
|
|
77
|
+
})));
|
|
78
|
+
//ensure that we're fully initialized before proceeding
|
|
79
|
+
await this.onInitialized();
|
|
80
|
+
const actions = [...event.actions];
|
|
81
|
+
let idSequence = 0;
|
|
82
|
+
//add an ID to every action (so we can track which actions were handled by which projects)
|
|
83
|
+
for (const action of actions) {
|
|
84
|
+
action.id = idSequence++;
|
|
85
|
+
}
|
|
86
|
+
//apply all of the document actions to each project in parallel
|
|
87
|
+
const responses = await Promise.all(this.projects.map(async (project) => {
|
|
88
|
+
//wait for this project to finish activating
|
|
89
|
+
await project.whenActivated();
|
|
90
|
+
const filterer = new PathFilterer_1.PathCollection({
|
|
91
|
+
rootDir: project.rootDir,
|
|
92
|
+
globs: project.filePatterns
|
|
93
|
+
});
|
|
94
|
+
// only include files that are applicable to this specific project (still allow deletes to flow through since they're cheap)
|
|
95
|
+
const projectActions = actions.filter(action => {
|
|
96
|
+
return (
|
|
97
|
+
//if this is a delete, just pass it through because they're cheap to apply
|
|
98
|
+
action.type === 'delete' ||
|
|
99
|
+
//if this is a set, only pass it through if it's a file that this project cares about
|
|
100
|
+
filterer.isMatch(action.srcPath));
|
|
101
|
+
});
|
|
102
|
+
if (projectActions.length > 0) {
|
|
103
|
+
const responseActions = await project.applyFileChanges(projectActions);
|
|
104
|
+
return responseActions.map(x => ({
|
|
105
|
+
project: project,
|
|
106
|
+
action: x
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
}));
|
|
110
|
+
//create standalone projects for any files not handled by any project
|
|
111
|
+
const flatResponses = responses.flat();
|
|
112
|
+
for (const action of actions) {
|
|
113
|
+
//skip this action if it doesn't support standalone projects
|
|
114
|
+
if (!action.allowStandaloneProject || action.type !== 'set') {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
//a list of responses that handled this action
|
|
118
|
+
const handledResponses = flatResponses.filter(x => { var _a, _b; return ((_a = x === null || x === void 0 ? void 0 : x.action) === null || _a === void 0 ? void 0 : _a.id) === action.id && ((_b = x === null || x === void 0 ? void 0 : x.action) === null || _b === void 0 ? void 0 : _b.status) === 'accepted'; });
|
|
119
|
+
//remove any standalone project created for this file since it was handled by a normal project
|
|
120
|
+
const normalProjectsThatHandledThisFile = handledResponses.filter(x => !x.project.isStandaloneProject);
|
|
121
|
+
if (normalProjectsThatHandledThisFile.length > 0) {
|
|
122
|
+
//if there's a standalone project for this file, delete it
|
|
123
|
+
if (this.getStandaloneProject(action.srcPath, false)) {
|
|
124
|
+
this.logger.debug(`flushDocumentChanges: removing standalone project because the following normal projects handled the file: '${action.srcPath}', projects:`, normalProjectsThatHandledThisFile.map(x => x.project.projectIdentifier));
|
|
125
|
+
this.removeStandaloneProject(action.srcPath);
|
|
126
|
+
}
|
|
127
|
+
// create a standalone project if this action was handled by zero normal projects.
|
|
128
|
+
//(safe to call even if there's already a standalone project, won't create dupes)
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
//TODO only create standalone projects for files we understand (brightscript, brighterscript, scenegraph xml, etc)
|
|
132
|
+
await this.createStandaloneProject(action.srcPath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
this.logger.info('flushDocumentChanges complete', actions.map(x => ({
|
|
136
|
+
type: x.type,
|
|
137
|
+
srcPath: x.srcPath,
|
|
138
|
+
allowStandaloneProject: x.allowStandaloneProject
|
|
139
|
+
})));
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get a standalone project for a given file path
|
|
143
|
+
*/
|
|
144
|
+
getStandaloneProject(srcPath, standardizePath = true) {
|
|
145
|
+
return this.standaloneProjects.get(standardizePath ? util_1.util.standardizePath(srcPath) : srcPath);
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create a project that validates a single file. This is useful for getting language support for files that don't belong to a project
|
|
149
|
+
*/
|
|
150
|
+
async createStandaloneProject(srcPath) {
|
|
151
|
+
srcPath = util_1.util.standardizePath(srcPath);
|
|
152
|
+
//if we already have a standalone project with this path, do nothing because it already exists
|
|
153
|
+
if (this.getStandaloneProject(srcPath, false)) {
|
|
154
|
+
this.logger.log('createStandaloneProject skipping because we already have one for this path');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
this.logger.log(`Creating standalone project for '${srcPath}'`);
|
|
158
|
+
const projectNumber = ProjectManager.projectNumberSequence++;
|
|
159
|
+
const rootDir = path.join(__dirname, `standalone-project-${projectNumber}`);
|
|
160
|
+
const projectOptions = {
|
|
161
|
+
//these folders don't matter for standalone projects
|
|
162
|
+
workspaceFolder: rootDir,
|
|
163
|
+
projectPath: rootDir,
|
|
164
|
+
enableThreading: false,
|
|
165
|
+
projectNumber: projectNumber,
|
|
166
|
+
files: [{
|
|
167
|
+
src: srcPath,
|
|
168
|
+
dest: 'source/standalone.brs'
|
|
169
|
+
}]
|
|
170
|
+
};
|
|
171
|
+
const project = this.constructProject(projectOptions);
|
|
172
|
+
project.srcPath = srcPath;
|
|
173
|
+
project.isStandaloneProject = true;
|
|
174
|
+
this.standaloneProjects.set(srcPath, project);
|
|
175
|
+
await this.activateProject(project, projectOptions);
|
|
176
|
+
}
|
|
177
|
+
removeStandaloneProject(srcPath) {
|
|
178
|
+
srcPath = util_1.util.standardizePath(srcPath);
|
|
179
|
+
const project = this.getStandaloneProject(srcPath, false);
|
|
180
|
+
if (project) {
|
|
181
|
+
if (project.srcPath === srcPath) {
|
|
182
|
+
this.logger.debug(`Removing standalone project for file '${srcPath}'`);
|
|
183
|
+
this.removeProject(project);
|
|
184
|
+
this.standaloneProjects.delete(srcPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Get a promise that resolves when this manager is finished initializing
|
|
190
|
+
*/
|
|
191
|
+
onInitialized() {
|
|
192
|
+
return Promise.allSettled([
|
|
193
|
+
//wait for the first sync to finish
|
|
194
|
+
this.firstSync.promise,
|
|
195
|
+
//make sure we're not in the middle of a sync
|
|
196
|
+
this.syncPromise,
|
|
197
|
+
//make sure all projects are activated
|
|
198
|
+
...this.projects.map(x => x.whenActivated())
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Get a promise that resolves when the project manager is idle (no pending work)
|
|
203
|
+
*/
|
|
204
|
+
async onIdle() {
|
|
205
|
+
await this.onInitialized();
|
|
206
|
+
//There are race conditions where the fileChangesQueue will become idle, but that causes the documentManager
|
|
207
|
+
//to start a new flush. So we must keep waiting until everything is idle
|
|
208
|
+
while (!this.documentManager.isIdle || !this.fileChangesQueue.isIdle) {
|
|
209
|
+
this.logger.debug('onIdle', { documentManagerIdle: this.documentManager.isIdle, fileChangesQueueIdle: this.fileChangesQueue.isIdle });
|
|
210
|
+
await Promise.allSettled([
|
|
211
|
+
//make sure all pending file changes have been flushed
|
|
212
|
+
this.documentManager.onIdle(),
|
|
213
|
+
//wait for the file changes queue to be idle
|
|
214
|
+
this.fileChangesQueue.onIdle()
|
|
215
|
+
]);
|
|
216
|
+
}
|
|
217
|
+
this.logger.info('onIdle debug', { documentManagerIdle: this.documentManager.isIdle, fileChangesQueueIdle: this.fileChangesQueue.isIdle });
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Given a list of all desired projects, create any missing projects and destroy and projects that are no longer available
|
|
221
|
+
* Treat workspaces that don't have a bsconfig.json as a project.
|
|
222
|
+
* Handle situations where bsconfig.json files were added or removed (to elevate/lower workspaceFolder projects accordingly)
|
|
223
|
+
* Leave existing projects alone if they are not affected by these changes
|
|
224
|
+
* @param workspaceConfigs an array of workspaces
|
|
225
|
+
*/
|
|
226
|
+
async syncProjects(workspaceConfigs, forceReload = false) {
|
|
227
|
+
//if we're force reloading, destroy all projects and start fresh
|
|
228
|
+
if (forceReload) {
|
|
229
|
+
this.logger.log('syncProjects: forceReload is true so removing all existing projects');
|
|
230
|
+
for (const project of this.projects) {
|
|
231
|
+
this.removeProject(project);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
this.logger.log('syncProjects', workspaceConfigs.map(x => x.workspaceFolder));
|
|
235
|
+
this.syncPromise = (async () => {
|
|
236
|
+
//build a list of unique projects across all workspace folders
|
|
237
|
+
let projectConfigs = (await Promise.all(workspaceConfigs.map(async (workspaceConfig) => {
|
|
238
|
+
const projectPaths = await this.getProjectPaths(workspaceConfig);
|
|
239
|
+
return projectPaths.map(projectPath => ({
|
|
240
|
+
projectPath: (0, util_1.standardizePath) `${projectPath}`,
|
|
241
|
+
workspaceFolder: (0, util_1.standardizePath) `${workspaceConfig.workspaceFolder}`,
|
|
242
|
+
excludePatterns: workspaceConfig.excludePatterns,
|
|
243
|
+
enableThreading: workspaceConfig.enableThreading
|
|
244
|
+
}));
|
|
245
|
+
}))).flat(1);
|
|
246
|
+
//filter the project paths to only include those that are allowed by the path filterer
|
|
247
|
+
projectConfigs = this.pathFilterer.filter(projectConfigs, x => x.projectPath);
|
|
248
|
+
//delete projects not represented in the list
|
|
249
|
+
for (const project of this.projects) {
|
|
250
|
+
//we can't find this existing project in our new list, so scrap it
|
|
251
|
+
if (!projectConfigs.find(x => x.projectPath === project.projectPath)) {
|
|
252
|
+
this.removeProject(project);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// skip projects we already have (they're already loaded...no need to reload them)
|
|
256
|
+
projectConfigs = projectConfigs.filter(x => {
|
|
257
|
+
return !this.hasProject(x.projectPath);
|
|
258
|
+
});
|
|
259
|
+
//dedupe by project path
|
|
260
|
+
projectConfigs = [
|
|
261
|
+
...projectConfigs.reduce((acc, x) => acc.set(x.projectPath, x), new Map()).values()
|
|
262
|
+
];
|
|
263
|
+
//create missing projects
|
|
264
|
+
await Promise.all(projectConfigs.map(async (config) => {
|
|
265
|
+
await this.createAndActivateProject(config);
|
|
266
|
+
}));
|
|
267
|
+
//mark that we've completed our first sync
|
|
268
|
+
this.firstSync.tryResolve();
|
|
269
|
+
})();
|
|
270
|
+
//return the sync promise
|
|
271
|
+
return this.syncPromise;
|
|
272
|
+
}
|
|
273
|
+
handleFileChanges(changes) {
|
|
274
|
+
this.logger.debug('handleFileChanges', changes.map(x => `${FileChangeTypeLookup[x.type]}: ${x.srcPath}`));
|
|
275
|
+
//this function should NOT be marked as async, because typescript wraps the body in an async call sometimes. These need to be registered synchronously
|
|
276
|
+
return this.fileChangesQueue.run(async (changes) => {
|
|
277
|
+
//wait for any pending syncs to finish
|
|
278
|
+
await this.onInitialized();
|
|
279
|
+
return this._handleFileChanges(changes);
|
|
280
|
+
}, changes);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Handle when files or directories are added, changed, or deleted in the workspace.
|
|
284
|
+
* This is safe to call any time. Changes will be queued and flushed at the correct times
|
|
285
|
+
*/
|
|
286
|
+
async _handleFileChanges(changes) {
|
|
287
|
+
//normalize srcPath for all changes
|
|
288
|
+
for (const change of changes) {
|
|
289
|
+
change.srcPath = util_1.util.standardizePath(change.srcPath);
|
|
290
|
+
}
|
|
291
|
+
//filter any changes that are not allowed by the path filterer
|
|
292
|
+
changes = this.pathFilterer.filter(changes, x => x.srcPath);
|
|
293
|
+
this.logger.debug('handleFileChanges -> filtered', changes.map(x => `${FileChangeTypeLookup[x.type]}: ${x.srcPath}`));
|
|
294
|
+
//process all file changes in parallel
|
|
295
|
+
await Promise.all(changes.map(async (change) => {
|
|
296
|
+
await this.handleFileChange(change);
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Handle a single file change. If the file is a directory, this will recursively read all files in the directory and call `handleFileChanges` again
|
|
301
|
+
*/
|
|
302
|
+
async handleFileChange(change) {
|
|
303
|
+
if (change.type === vscode_languageserver_protocol_1.FileChangeType.Deleted) {
|
|
304
|
+
//mark this document or directory as deleted
|
|
305
|
+
this.documentManager.delete(change.srcPath);
|
|
306
|
+
//file added or changed
|
|
307
|
+
}
|
|
308
|
+
else {
|
|
309
|
+
//if this is a new directory, read all files recursively and register those as file changes too
|
|
310
|
+
if (util_1.util.isDirectorySync(change.srcPath)) {
|
|
311
|
+
const files = await fastGlob('**/*', {
|
|
312
|
+
cwd: change.srcPath,
|
|
313
|
+
onlyFiles: true,
|
|
314
|
+
absolute: true
|
|
315
|
+
});
|
|
316
|
+
//pipe all files found recursively in the new directory through this same function so they can be processed correctly
|
|
317
|
+
await Promise.all(files.map((srcPath) => {
|
|
318
|
+
return this.handleFileChange({
|
|
319
|
+
srcPath: util_1.util.standardizePath(srcPath),
|
|
320
|
+
type: vscode_languageserver_protocol_1.FileChangeType.Changed,
|
|
321
|
+
allowStandaloneProject: change.allowStandaloneProject
|
|
322
|
+
});
|
|
323
|
+
}));
|
|
324
|
+
//this is a new file. set the file contents
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
this.documentManager.set({
|
|
328
|
+
srcPath: change.srcPath,
|
|
329
|
+
fileContents: change.fileContents,
|
|
330
|
+
allowStandaloneProject: change.allowStandaloneProject
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
//reload any projects whose bsconfig.json was changed
|
|
335
|
+
const projectsToReload = this.projects.filter(project => {
|
|
336
|
+
var _a;
|
|
337
|
+
//this is a path to a bsconfig.json file
|
|
338
|
+
if (((_a = project.bsconfigPath) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === change.srcPath.toLowerCase()) {
|
|
339
|
+
//fetch file contents if we don't already have them
|
|
340
|
+
if (!change.fileContents) {
|
|
341
|
+
try {
|
|
342
|
+
change.fileContents = fsExtra.readFileSync(project.bsconfigPath).toString();
|
|
343
|
+
}
|
|
344
|
+
finally { }
|
|
345
|
+
}
|
|
346
|
+
///the bsconfig contents have changed since we last saw it, so reload this project
|
|
347
|
+
if (project.bsconfigFileContents !== change.fileContents) {
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return false;
|
|
352
|
+
});
|
|
353
|
+
if (projectsToReload.length > 0) {
|
|
354
|
+
await Promise.all(projectsToReload.map(x => this.reloadProject(x)));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Handle when a file is closed in the editor (this mostly just handles removing standalone projects)
|
|
359
|
+
*/
|
|
360
|
+
async handleFileClose(event) {
|
|
361
|
+
this.logger.debug(`File was closed. ${event.srcPath}`);
|
|
362
|
+
this.removeStandaloneProject(event.srcPath);
|
|
363
|
+
//most other methods on this class are async, might as well make this one async too for consistency and future expansion
|
|
364
|
+
await Promise.resolve();
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Given a project, forcibly reload it by removing it and re-adding it
|
|
368
|
+
*/
|
|
369
|
+
async reloadProject(project) {
|
|
370
|
+
this.logger.log('Reloading project', { projectPath: project.projectPath });
|
|
371
|
+
this.removeProject(project);
|
|
372
|
+
project = await this.createAndActivateProject(project.activateOptions);
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get all the semantic tokens for the given file
|
|
376
|
+
* @returns an array of semantic tokens
|
|
377
|
+
*/
|
|
378
|
+
async getSemanticTokens(options) {
|
|
379
|
+
//wait for all pending syncs to finish
|
|
380
|
+
await this.onIdle();
|
|
381
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getSemanticTokens(options)),
|
|
382
|
+
//keep the first non-falsey result
|
|
383
|
+
(result) => (result === null || result === void 0 ? void 0 : result.length) > 0);
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Get a string containing the transpiled contents of the file at the given path
|
|
388
|
+
* @returns the transpiled contents of the file as a string
|
|
389
|
+
*/
|
|
390
|
+
async transpileFile(options) {
|
|
391
|
+
//wait for all pending syncs to finish
|
|
392
|
+
await this.onIdle();
|
|
393
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.transpileFile(options)),
|
|
394
|
+
//keep the first non-falsey result
|
|
395
|
+
(result) => !!result);
|
|
396
|
+
return result;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get the completions for the given position in the file
|
|
400
|
+
*/
|
|
401
|
+
async getCompletions(options) {
|
|
402
|
+
var _a;
|
|
403
|
+
await this.onIdle();
|
|
404
|
+
//if the request has been cancelled since originally requested due to idle time being slow, skip the rest of the wor
|
|
405
|
+
if ((_a = options === null || options === void 0 ? void 0 : options.cancellationToken) === null || _a === void 0 ? void 0 : _a.isCancellationRequested) {
|
|
406
|
+
this.logger.log('ProjectManager getCompletions cancelled', options);
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
this.logger.log('ProjectManager getCompletions', options);
|
|
410
|
+
//Ask every project for results, keep whichever one responds first that has a valid response
|
|
411
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getCompletions(options)),
|
|
412
|
+
//keep the first non-falsey result
|
|
413
|
+
(result) => { var _a; return ((_a = result === null || result === void 0 ? void 0 : result.items) === null || _a === void 0 ? void 0 : _a.length) > 0; });
|
|
414
|
+
return result;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Get the hover information for the given position in the file. If multiple projects have hover information, the projects will be raced and
|
|
418
|
+
* the fastest result will be returned
|
|
419
|
+
* @returns the hover information or undefined if no hover information was found
|
|
420
|
+
*/
|
|
421
|
+
async getHover(options) {
|
|
422
|
+
//wait for all pending syncs to finish
|
|
423
|
+
await this.onIdle();
|
|
424
|
+
//Ask every project for hover info, keep whichever one responds first that has a valid response
|
|
425
|
+
let hover = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getHover(options)),
|
|
426
|
+
//keep the first set of non-empty results
|
|
427
|
+
(result) => (result === null || result === void 0 ? void 0 : result.length) > 0);
|
|
428
|
+
return hover === null || hover === void 0 ? void 0 : hover[0];
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Get the definition for the symbol at the given position in the file
|
|
432
|
+
* @returns a list of locations where the symbol under the position is defined in the project
|
|
433
|
+
*/
|
|
434
|
+
async getDefinition(options) {
|
|
435
|
+
//wait for all pending syncs to finish
|
|
436
|
+
await this.onIdle();
|
|
437
|
+
//TODO should we merge definitions across ALL projects? or just return definitions from the first project we found
|
|
438
|
+
//Ask every project for definition info, keep whichever one responds first that has a valid response
|
|
439
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getDefinition(options)),
|
|
440
|
+
//keep the first non-falsey result
|
|
441
|
+
(result) => !!result);
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
async getSignatureHelp(options) {
|
|
445
|
+
var _a;
|
|
446
|
+
//wait for all pending syncs to finish
|
|
447
|
+
await this.onIdle();
|
|
448
|
+
//Ask every project for definition info, keep whichever one responds first that has a valid response
|
|
449
|
+
let signatures = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getSignatureHelp(options)),
|
|
450
|
+
//keep the first non-falsey result
|
|
451
|
+
(result) => !!result);
|
|
452
|
+
if ((signatures === null || signatures === void 0 ? void 0 : signatures.length) > 0) {
|
|
453
|
+
const activeSignature = signatures.length > 0 ? 0 : undefined;
|
|
454
|
+
const activeParameter = activeSignature >= 0 ? (_a = signatures[activeSignature]) === null || _a === void 0 ? void 0 : _a.index : undefined;
|
|
455
|
+
let result = {
|
|
456
|
+
signatures: signatures.map((s) => s.signature),
|
|
457
|
+
activeSignature: activeSignature,
|
|
458
|
+
activeParameter: activeParameter
|
|
459
|
+
};
|
|
460
|
+
return result;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
async getDocumentSymbol(options) {
|
|
464
|
+
//wait for all pending syncs to finish
|
|
465
|
+
await this.onIdle();
|
|
466
|
+
//Ask every project for definition info, keep whichever one responds first that has a valid response
|
|
467
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getDocumentSymbol(options)),
|
|
468
|
+
//keep the first non-falsey result
|
|
469
|
+
(result) => !!result);
|
|
470
|
+
return result;
|
|
471
|
+
}
|
|
472
|
+
async getWorkspaceSymbol() {
|
|
473
|
+
//wait for all pending syncs to finish
|
|
474
|
+
await this.onIdle();
|
|
475
|
+
//Ask every project for definition info, keep whichever one responds first that has a valid response
|
|
476
|
+
let responses = await Promise.allSettled(this.projects.map(x => x.getWorkspaceSymbol()));
|
|
477
|
+
let results = responses
|
|
478
|
+
//keep all symbol results
|
|
479
|
+
.map((x) => {
|
|
480
|
+
return x.status === 'fulfilled' ? x.value : [];
|
|
481
|
+
})
|
|
482
|
+
//flatten the array
|
|
483
|
+
.flat()
|
|
484
|
+
//throw out nulls
|
|
485
|
+
.filter(x => !!x);
|
|
486
|
+
// Remove duplicates
|
|
487
|
+
const allSymbols = Object.values(results.reduce((map, symbol) => {
|
|
488
|
+
const key = symbol.location.uri + symbol.name;
|
|
489
|
+
map[key] = symbol;
|
|
490
|
+
return map;
|
|
491
|
+
}, {}));
|
|
492
|
+
return allSymbols;
|
|
493
|
+
}
|
|
494
|
+
async getReferences(options) {
|
|
495
|
+
//wait for all pending syncs to finish
|
|
496
|
+
await this.onIdle();
|
|
497
|
+
//Ask every project for definition info, keep whichever one responds first that has a valid response
|
|
498
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getReferences(options)),
|
|
499
|
+
//keep the first non-falsey result
|
|
500
|
+
(result) => !!result);
|
|
501
|
+
return result !== null && result !== void 0 ? result : [];
|
|
502
|
+
}
|
|
503
|
+
async getCodeActions(options) {
|
|
504
|
+
//wait for all pending syncs to finish
|
|
505
|
+
await this.onIdle();
|
|
506
|
+
//Ask every project for definition info, keep whichever one responds first that has a valid response
|
|
507
|
+
let result = await util_1.util.promiseRaceMatch(this.projects.map(x => x.getCodeActions(options)),
|
|
508
|
+
//keep the first non-falsey result
|
|
509
|
+
(result) => !!result);
|
|
510
|
+
return result;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Scan a given workspace for all `bsconfig.json` files. If at least one is found, then only folders who have bsconfig.json are returned.
|
|
514
|
+
* If none are found, then the workspaceFolder itself is treated as a project
|
|
515
|
+
*/
|
|
516
|
+
async getProjectPaths(workspaceConfig) {
|
|
517
|
+
var _a;
|
|
518
|
+
//get the list of exclude patterns, and negate them (so they actually work like excludes)
|
|
519
|
+
const excludePatterns = ((_a = workspaceConfig.excludePatterns) !== null && _a !== void 0 ? _a : []).map(x => (0, util_1.standardizePath) `!${x}`);
|
|
520
|
+
let files = await roku_deploy_1.rokuDeploy.getFilePaths([
|
|
521
|
+
'**/bsconfig.json',
|
|
522
|
+
//exclude all files found in `files.exclude`
|
|
523
|
+
...excludePatterns
|
|
524
|
+
], workspaceConfig.workspaceFolder);
|
|
525
|
+
//filter the files to only include those that are allowed by the path filterer
|
|
526
|
+
files = this.pathFilterer.filter(files, x => x.src);
|
|
527
|
+
//if we found at least one bsconfig.json, then ALL projects must have a bsconfig.json.
|
|
528
|
+
if (files.length > 0) {
|
|
529
|
+
return files.map(file => (0, util_1.standardizePath) `${path.dirname(file.src)}`);
|
|
530
|
+
}
|
|
531
|
+
//look for roku project folders
|
|
532
|
+
let rokuLikeDirs = (await Promise.all(
|
|
533
|
+
//find all folders containing a `manifest` file
|
|
534
|
+
(await roku_deploy_1.rokuDeploy.getFilePaths([
|
|
535
|
+
'**/manifest',
|
|
536
|
+
...excludePatterns
|
|
537
|
+
//is there at least one .bs|.brs file under the `/source` folder?
|
|
538
|
+
], workspaceConfig.workspaceFolder)).map(async (manifestEntry) => {
|
|
539
|
+
const manifestDir = path.dirname(manifestEntry.src);
|
|
540
|
+
const files = await roku_deploy_1.rokuDeploy.getFilePaths([
|
|
541
|
+
'source/**/*.{brs,bs}',
|
|
542
|
+
...excludePatterns
|
|
543
|
+
], manifestDir);
|
|
544
|
+
if (files.length > 0) {
|
|
545
|
+
return manifestDir;
|
|
546
|
+
}
|
|
547
|
+
})
|
|
548
|
+
//throw out nulls
|
|
549
|
+
)).filter(x => !!x);
|
|
550
|
+
//throw out any directories that are not allowed by the path filterer
|
|
551
|
+
rokuLikeDirs = this.pathFilterer.filter(rokuLikeDirs, srcPath => srcPath);
|
|
552
|
+
if (rokuLikeDirs.length > 0) {
|
|
553
|
+
return rokuLikeDirs;
|
|
554
|
+
}
|
|
555
|
+
//treat the workspace folder as a brightscript project itself
|
|
556
|
+
return [workspaceConfig.workspaceFolder];
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Returns true if we have this project, or false if we don't
|
|
560
|
+
* @param projectPath path to the project
|
|
561
|
+
* @returns true if the project exists, or false if it doesn't
|
|
562
|
+
*/
|
|
563
|
+
hasProject(projectPath) {
|
|
564
|
+
return !!this.getProject(projectPath);
|
|
565
|
+
}
|
|
566
|
+
/**
|
|
567
|
+
* Get a project with the specified path
|
|
568
|
+
* @param param path to the project or an obj that has `projectPath` prop
|
|
569
|
+
* @returns a project, or undefined if no project was found
|
|
570
|
+
*/
|
|
571
|
+
getProject(param) {
|
|
572
|
+
const projectPath = util_1.util.standardizePath((typeof param === 'string') ? param : param.projectPath);
|
|
573
|
+
return this.projects.find(x => x.projectPath === projectPath);
|
|
574
|
+
}
|
|
575
|
+
/**
|
|
576
|
+
* Remove a project from the language server
|
|
577
|
+
*/
|
|
578
|
+
removeProject(project) {
|
|
579
|
+
const idx = this.projects.findIndex(x => x.projectPath === (project === null || project === void 0 ? void 0 : project.projectPath));
|
|
580
|
+
if (idx > -1) {
|
|
581
|
+
this.logger.log('Removing project', { projectPath: project.projectPath, projectNumber: project.projectNumber });
|
|
582
|
+
this.projects.splice(idx, 1);
|
|
583
|
+
}
|
|
584
|
+
//anytime we remove a project, we should emit an event that clears all of its diagnostics
|
|
585
|
+
this.emit('diagnostics', { project: project, diagnostics: [] });
|
|
586
|
+
project === null || project === void 0 ? void 0 : project.dispose();
|
|
587
|
+
this.busyStatusTracker.endAllRunsForScope(project);
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* Get a projectNumber for a given config. Try to reuse project numbers when we've seen this project before
|
|
591
|
+
* - If the config already has one, use that.
|
|
592
|
+
* - If we've already seen this config before, use the same project number as before
|
|
593
|
+
*/
|
|
594
|
+
getProjectNumber(config) {
|
|
595
|
+
if (config.projectNumber !== undefined) {
|
|
596
|
+
return config.projectNumber;
|
|
597
|
+
}
|
|
598
|
+
return ProjectManager.projectNumberCache.getOrAdd(`${(0, util_1.standardizePath)(config.projectPath)}-${(0, util_1.standardizePath)(config.workspaceFolder)}-${config.bsconfigPath}`, () => {
|
|
599
|
+
return ProjectManager.projectNumberSequence++;
|
|
600
|
+
});
|
|
601
|
+
}
|
|
602
|
+
/**
|
|
603
|
+
* Constructs a project for the given config. Just makes the project, doesn't activate it
|
|
604
|
+
* @returns a new project, or the existing project if one already exists with this config info
|
|
605
|
+
*/
|
|
606
|
+
constructProject(config) {
|
|
607
|
+
//skip this project if we already have it
|
|
608
|
+
if (this.hasProject(config.projectPath)) {
|
|
609
|
+
return this.getProject(config.projectPath);
|
|
610
|
+
}
|
|
611
|
+
config.projectNumber = this.getProjectNumber(config);
|
|
612
|
+
const projectIdentifier = `prj${config.projectNumber}`;
|
|
613
|
+
let project = config.enableThreading
|
|
614
|
+
? new WorkerThreadProject_1.WorkerThreadProject({
|
|
615
|
+
logger: this.logger.createLogger(),
|
|
616
|
+
projectIdentifier: projectIdentifier
|
|
617
|
+
})
|
|
618
|
+
: new Project_1.Project({
|
|
619
|
+
logger: this.logger.createLogger(),
|
|
620
|
+
projectIdentifier: projectIdentifier
|
|
621
|
+
});
|
|
622
|
+
this.logger.log(`Created project #${config.projectNumber} for: "${config.projectPath}" (${config.enableThreading ? 'worker thread' : 'main thread'})`);
|
|
623
|
+
this.projects.push(project);
|
|
624
|
+
//pipe all project-specific events through our emitter, and include the project reference
|
|
625
|
+
project.on('all', (eventName, data) => {
|
|
626
|
+
this.emit(eventName, Object.assign(Object.assign({}, data), { project: project }));
|
|
627
|
+
});
|
|
628
|
+
return project;
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Constructs a project for the given config
|
|
632
|
+
* @returns a new project, or the existing project if one already exists with this config info
|
|
633
|
+
*/
|
|
634
|
+
async createAndActivateProject(config) {
|
|
635
|
+
//skip this project if we already have it
|
|
636
|
+
if (this.hasProject(config.projectPath)) {
|
|
637
|
+
return this.getProject(config.projectPath);
|
|
638
|
+
}
|
|
639
|
+
const project = this.constructProject(config);
|
|
640
|
+
await this.activateProject(project, config);
|
|
641
|
+
return project;
|
|
642
|
+
}
|
|
643
|
+
async activateProject(project, config) {
|
|
644
|
+
this.logger.debug('Activating project', project.projectIdentifier, {
|
|
645
|
+
projectPath: config === null || config === void 0 ? void 0 : config.projectPath,
|
|
646
|
+
bsconfigPath: config.bsconfigPath
|
|
647
|
+
});
|
|
648
|
+
await project.activate(config);
|
|
649
|
+
//send an event to indicate that this project has been activated
|
|
650
|
+
this.emit('project-activate', { project: project });
|
|
651
|
+
//register this project's list of files with the path filterer
|
|
652
|
+
const unregister = this.pathFilterer.registerIncludeList(project.rootDir, project.filePatterns);
|
|
653
|
+
project.disposables.push({ dispose: unregister });
|
|
654
|
+
}
|
|
655
|
+
on(eventName, handler) {
|
|
656
|
+
this.emitter.on(eventName, handler);
|
|
657
|
+
return () => {
|
|
658
|
+
this.emitter.removeListener(eventName, handler);
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
async emit(eventName, data) {
|
|
662
|
+
//emit these events on next tick, otherwise they will be processed immediately which could cause issues
|
|
663
|
+
await util_1.util.sleep(0);
|
|
664
|
+
this.emitter.emit(eventName, data);
|
|
665
|
+
}
|
|
666
|
+
dispose() {
|
|
667
|
+
var _a;
|
|
668
|
+
this.emitter.removeAllListeners();
|
|
669
|
+
for (const project of this.projects) {
|
|
670
|
+
(_a = project === null || project === void 0 ? void 0 : project.dispose) === null || _a === void 0 ? void 0 : _a.call(project);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
ProjectManager.documentManagerDelay = 150;
|
|
675
|
+
/**
|
|
676
|
+
* A unique project counter to help distinguish log entries in lsp mode
|
|
677
|
+
*/
|
|
678
|
+
ProjectManager.projectNumberSequence = 0;
|
|
679
|
+
ProjectManager.projectNumberCache = new Cache_1.Cache();
|
|
680
|
+
__decorate([
|
|
681
|
+
TrackBusyStatus
|
|
682
|
+
], ProjectManager.prototype, "flushDocumentChanges", null);
|
|
683
|
+
__decorate([
|
|
684
|
+
TrackBusyStatus
|
|
685
|
+
], ProjectManager.prototype, "syncProjects", null);
|
|
686
|
+
__decorate([
|
|
687
|
+
TrackBusyStatus
|
|
688
|
+
], ProjectManager.prototype, "getSemanticTokens", null);
|
|
689
|
+
__decorate([
|
|
690
|
+
TrackBusyStatus
|
|
691
|
+
], ProjectManager.prototype, "transpileFile", null);
|
|
692
|
+
__decorate([
|
|
693
|
+
TrackBusyStatus
|
|
694
|
+
], ProjectManager.prototype, "getCompletions", null);
|
|
695
|
+
__decorate([
|
|
696
|
+
TrackBusyStatus
|
|
697
|
+
], ProjectManager.prototype, "getHover", null);
|
|
698
|
+
__decorate([
|
|
699
|
+
TrackBusyStatus
|
|
700
|
+
], ProjectManager.prototype, "getDefinition", null);
|
|
701
|
+
__decorate([
|
|
702
|
+
TrackBusyStatus
|
|
703
|
+
], ProjectManager.prototype, "getSignatureHelp", null);
|
|
704
|
+
__decorate([
|
|
705
|
+
TrackBusyStatus
|
|
706
|
+
], ProjectManager.prototype, "getDocumentSymbol", null);
|
|
707
|
+
__decorate([
|
|
708
|
+
TrackBusyStatus
|
|
709
|
+
], ProjectManager.prototype, "getWorkspaceSymbol", null);
|
|
710
|
+
__decorate([
|
|
711
|
+
TrackBusyStatus
|
|
712
|
+
], ProjectManager.prototype, "getReferences", null);
|
|
713
|
+
__decorate([
|
|
714
|
+
TrackBusyStatus
|
|
715
|
+
], ProjectManager.prototype, "getCodeActions", null);
|
|
716
|
+
__decorate([
|
|
717
|
+
TrackBusyStatus
|
|
718
|
+
], ProjectManager.prototype, "createAndActivateProject", null);
|
|
719
|
+
__decorate([
|
|
720
|
+
TrackBusyStatus
|
|
721
|
+
], ProjectManager.prototype, "activateProject", null);
|
|
722
|
+
exports.ProjectManager = ProjectManager;
|
|
723
|
+
/**
|
|
724
|
+
* An annotation used to wrap the method in a busyStatus tracking call
|
|
725
|
+
*/
|
|
726
|
+
function TrackBusyStatus(target, propertyKey, descriptor) {
|
|
727
|
+
let originalMethod = descriptor.value;
|
|
728
|
+
//wrapping the original method
|
|
729
|
+
descriptor.value = function value(...args) {
|
|
730
|
+
return this.busyStatusTracker.run(() => {
|
|
731
|
+
return originalMethod.apply(this, args);
|
|
732
|
+
}, originalMethod.name);
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
//# sourceMappingURL=ProjectManager.js.map
|