brighterscript 1.0.0-alpha.43 → 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.
Files changed (200) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/bsconfig.schema.json +6 -1
  3. package/dist/AstValidationSegmenter.js +6 -1
  4. package/dist/AstValidationSegmenter.js.map +1 -1
  5. package/dist/BsConfig.d.ts +4 -0
  6. package/dist/BusyStatusTracker.d.ts +37 -7
  7. package/dist/BusyStatusTracker.js +73 -8
  8. package/dist/BusyStatusTracker.js.map +1 -1
  9. package/dist/Cache.d.ts +0 -4
  10. package/dist/Cache.js +0 -6
  11. package/dist/Cache.js.map +1 -1
  12. package/dist/CrossScopeValidator.d.ts +1 -1
  13. package/dist/CrossScopeValidator.js +4 -4
  14. package/dist/CrossScopeValidator.js.map +1 -1
  15. package/dist/DiagnosticCollection.d.ts +19 -5
  16. package/dist/DiagnosticCollection.js +71 -23
  17. package/dist/DiagnosticCollection.js.map +1 -1
  18. package/dist/DiagnosticFilterer.d.ts +14 -1
  19. package/dist/DiagnosticFilterer.js +130 -12
  20. package/dist/DiagnosticFilterer.js.map +1 -1
  21. package/dist/DiagnosticManager.d.ts +11 -1
  22. package/dist/DiagnosticManager.js +192 -35
  23. package/dist/DiagnosticManager.js.map +1 -1
  24. package/dist/LanguageServer.d.ts +82 -133
  25. package/dist/LanguageServer.js +403 -942
  26. package/dist/LanguageServer.js.map +1 -1
  27. package/dist/Logger.d.ts +9 -4
  28. package/dist/Logger.js +30 -6
  29. package/dist/Logger.js.map +1 -1
  30. package/dist/PluginInterface.d.ts +8 -8
  31. package/dist/PluginInterface.js.map +1 -1
  32. package/dist/Program.d.ts +23 -4
  33. package/dist/Program.js +294 -194
  34. package/dist/Program.js.map +1 -1
  35. package/dist/ProgramBuilder.d.ts +22 -7
  36. package/dist/ProgramBuilder.js +44 -21
  37. package/dist/ProgramBuilder.js.map +1 -1
  38. package/dist/Scope.d.ts +11 -6
  39. package/dist/Scope.js +60 -36
  40. package/dist/Scope.js.map +1 -1
  41. package/dist/SemanticTokenUtils.js +1 -1
  42. package/dist/SemanticTokenUtils.js.map +1 -1
  43. package/dist/SymbolTable.js +4 -3
  44. package/dist/SymbolTable.js.map +1 -1
  45. package/dist/astUtils/reflection.d.ts +4 -4
  46. package/dist/astUtils/reflection.js +12 -10
  47. package/dist/astUtils/reflection.js.map +1 -1
  48. package/dist/bscPlugin/BscPlugin.d.ts +3 -3
  49. package/dist/bscPlugin/BscPlugin.js.map +1 -1
  50. package/dist/bscPlugin/CallExpressionInfo.js +4 -2
  51. package/dist/bscPlugin/CallExpressionInfo.js.map +1 -1
  52. package/dist/bscPlugin/completions/CompletionsProcessor.d.ts +1 -1
  53. package/dist/bscPlugin/completions/CompletionsProcessor.js +15 -15
  54. package/dist/bscPlugin/completions/CompletionsProcessor.js.map +1 -1
  55. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js +82 -5
  56. package/dist/bscPlugin/completions/CompletionsProcessor.spec.js.map +1 -1
  57. package/dist/bscPlugin/hover/HoverProcessor.js +3 -0
  58. package/dist/bscPlugin/hover/HoverProcessor.js.map +1 -1
  59. package/dist/bscPlugin/validation/ScopeValidator.d.ts +4 -1
  60. package/dist/bscPlugin/validation/ScopeValidator.js +161 -61
  61. package/dist/bscPlugin/validation/ScopeValidator.js.map +1 -1
  62. package/dist/common/Sequencer.d.ts +53 -0
  63. package/dist/common/Sequencer.js +232 -0
  64. package/dist/common/Sequencer.js.map +1 -0
  65. package/dist/common/Sequencer.spec.d.ts +1 -0
  66. package/dist/common/Sequencer.spec.js +75 -0
  67. package/dist/common/Sequencer.spec.js.map +1 -0
  68. package/dist/deferred.d.ts +2 -0
  69. package/dist/deferred.js +10 -0
  70. package/dist/deferred.js.map +1 -1
  71. package/dist/examples/plugins/removePrint.d.ts +2 -2
  72. package/dist/examples/plugins/removePrint.js.map +1 -1
  73. package/dist/files/BrsFile.d.ts +1 -1
  74. package/dist/files/BrsFile.js +11 -40
  75. package/dist/files/BrsFile.js.map +1 -1
  76. package/dist/files/BrsFile.spec.js +51 -2
  77. package/dist/files/BrsFile.spec.js.map +1 -1
  78. package/dist/files/BscFile.d.ts +1 -0
  79. package/dist/files/LazyFileData.d.ts +1 -0
  80. package/dist/files/XmlFile.spec.js +1 -1
  81. package/dist/files/XmlFile.spec.js.map +1 -1
  82. package/dist/globalCallables.js +186 -186
  83. package/dist/globalCallables.js.map +1 -1
  84. package/dist/interfaces.d.ts +56 -13
  85. package/dist/interfaces.js.map +1 -1
  86. package/dist/lexer/Lexer.js +1 -1
  87. package/dist/lexer/Lexer.js.map +1 -1
  88. package/dist/logging.d.ts +6 -1
  89. package/dist/logging.js +14 -1
  90. package/dist/logging.js.map +1 -1
  91. package/dist/lsp/ActionQueue.d.ts +35 -0
  92. package/dist/lsp/ActionQueue.js +115 -0
  93. package/dist/lsp/ActionQueue.js.map +1 -0
  94. package/dist/lsp/ActionQueue.spec.d.ts +1 -0
  95. package/dist/lsp/ActionQueue.spec.js +80 -0
  96. package/dist/lsp/ActionQueue.spec.js.map +1 -0
  97. package/dist/lsp/DocumentManager.d.ts +63 -0
  98. package/dist/lsp/DocumentManager.js +122 -0
  99. package/dist/lsp/DocumentManager.js.map +1 -0
  100. package/dist/lsp/DocumentManager.spec.d.ts +1 -0
  101. package/dist/lsp/DocumentManager.spec.js +103 -0
  102. package/dist/lsp/DocumentManager.spec.js.map +1 -0
  103. package/dist/lsp/LspProject.d.ts +231 -0
  104. package/dist/lsp/LspProject.js +3 -0
  105. package/dist/lsp/LspProject.js.map +1 -0
  106. package/dist/lsp/PathFilterer.d.ts +75 -0
  107. package/dist/lsp/PathFilterer.js +196 -0
  108. package/dist/lsp/PathFilterer.js.map +1 -0
  109. package/dist/lsp/PathFilterer.spec.d.ts +1 -0
  110. package/dist/lsp/PathFilterer.spec.js +182 -0
  111. package/dist/lsp/PathFilterer.spec.js.map +1 -0
  112. package/dist/lsp/Project.d.ts +167 -0
  113. package/dist/lsp/Project.js +432 -0
  114. package/dist/lsp/Project.js.map +1 -0
  115. package/dist/lsp/Project.spec.d.ts +1 -0
  116. package/dist/lsp/Project.spec.js +220 -0
  117. package/dist/lsp/Project.spec.js.map +1 -0
  118. package/dist/lsp/ProjectManager.d.ts +221 -0
  119. package/dist/lsp/ProjectManager.js +735 -0
  120. package/dist/lsp/ProjectManager.js.map +1 -0
  121. package/dist/lsp/ProjectManager.spec.d.ts +1 -0
  122. package/dist/lsp/ProjectManager.spec.js +757 -0
  123. package/dist/lsp/ProjectManager.spec.js.map +1 -0
  124. package/dist/lsp/ReaderWriterManager.d.ts +21 -0
  125. package/dist/lsp/ReaderWriterManager.js +60 -0
  126. package/dist/lsp/ReaderWriterManager.js.map +1 -0
  127. package/dist/lsp/worker/MessageHandler.d.ts +99 -0
  128. package/dist/lsp/worker/MessageHandler.js +138 -0
  129. package/dist/lsp/worker/MessageHandler.js.map +1 -0
  130. package/dist/lsp/worker/MessageHandler.spec.d.ts +1 -0
  131. package/dist/lsp/worker/MessageHandler.spec.js +64 -0
  132. package/dist/lsp/worker/MessageHandler.spec.js.map +1 -0
  133. package/dist/lsp/worker/WorkerPool.d.ts +38 -0
  134. package/dist/lsp/worker/WorkerPool.js +78 -0
  135. package/dist/lsp/worker/WorkerPool.js.map +1 -0
  136. package/dist/lsp/worker/WorkerPool.spec.d.ts +1 -0
  137. package/dist/lsp/worker/WorkerPool.spec.js +59 -0
  138. package/dist/lsp/worker/WorkerPool.spec.js.map +1 -0
  139. package/dist/lsp/worker/WorkerThreadProject.d.ts +144 -0
  140. package/dist/lsp/worker/WorkerThreadProject.js +183 -0
  141. package/dist/lsp/worker/WorkerThreadProject.js.map +1 -0
  142. package/dist/lsp/worker/WorkerThreadProject.spec.d.ts +2 -0
  143. package/dist/lsp/worker/WorkerThreadProject.spec.js +68 -0
  144. package/dist/lsp/worker/WorkerThreadProject.spec.js.map +1 -0
  145. package/dist/lsp/worker/WorkerThreadProjectRunner.d.ts +15 -0
  146. package/dist/lsp/worker/WorkerThreadProjectRunner.js +58 -0
  147. package/dist/lsp/worker/WorkerThreadProjectRunner.js.map +1 -0
  148. package/dist/parser/Expression.js +7 -2
  149. package/dist/parser/Expression.js.map +1 -1
  150. package/dist/parser/Parser.spec.js +12 -0
  151. package/dist/parser/Parser.spec.js.map +1 -1
  152. package/dist/parser/Statement.js +2 -2
  153. package/dist/parser/Statement.js.map +1 -1
  154. package/dist/parser/tests/expression/TemplateStringExpression.spec.js +51 -5
  155. package/dist/parser/tests/expression/TemplateStringExpression.spec.js.map +1 -1
  156. package/dist/roku-types/data.json +745 -751
  157. package/dist/types/BooleanType.d.ts +0 -2
  158. package/dist/types/BooleanType.js +4 -6
  159. package/dist/types/BooleanType.js.map +1 -1
  160. package/dist/types/BscType.js +5 -0
  161. package/dist/types/BscType.js.map +1 -1
  162. package/dist/types/BuiltInInterfaceAdder.d.ts +1 -0
  163. package/dist/types/BuiltInInterfaceAdder.js +24 -17
  164. package/dist/types/BuiltInInterfaceAdder.js.map +1 -1
  165. package/dist/types/DoubleType.d.ts +0 -2
  166. package/dist/types/DoubleType.js +4 -6
  167. package/dist/types/DoubleType.js.map +1 -1
  168. package/dist/types/DynamicType.d.ts +0 -2
  169. package/dist/types/DynamicType.js +3 -5
  170. package/dist/types/DynamicType.js.map +1 -1
  171. package/dist/types/FloatType.d.ts +0 -2
  172. package/dist/types/FloatType.js +4 -6
  173. package/dist/types/FloatType.js.map +1 -1
  174. package/dist/types/FunctionType.d.ts +0 -2
  175. package/dist/types/FunctionType.js +5 -7
  176. package/dist/types/FunctionType.js.map +1 -1
  177. package/dist/types/IntegerType.d.ts +0 -2
  178. package/dist/types/IntegerType.js +4 -6
  179. package/dist/types/IntegerType.js.map +1 -1
  180. package/dist/types/LongIntegerType.d.ts +0 -2
  181. package/dist/types/LongIntegerType.js +4 -6
  182. package/dist/types/LongIntegerType.js.map +1 -1
  183. package/dist/types/ObjectType.d.ts +3 -3
  184. package/dist/types/ObjectType.js +6 -8
  185. package/dist/types/ObjectType.js.map +1 -1
  186. package/dist/types/StringType.d.ts +0 -2
  187. package/dist/types/StringType.js +4 -6
  188. package/dist/types/StringType.js.map +1 -1
  189. package/dist/types/VoidType.d.ts +0 -2
  190. package/dist/types/VoidType.js +4 -6
  191. package/dist/types/VoidType.js.map +1 -1
  192. package/dist/types/helpers.d.ts +4 -0
  193. package/dist/types/helpers.js +5 -1
  194. package/dist/types/helpers.js.map +1 -1
  195. package/dist/util.d.ts +44 -16
  196. package/dist/util.js +196 -70
  197. package/dist/util.js.map +1 -1
  198. package/dist/validators/ClassValidator.js +2 -2
  199. package/dist/validators/ClassValidator.js.map +1 -1
  200. package/package.json +15 -5
@@ -7,38 +7,29 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
7
7
  };
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.NotificationName = exports.CustomCommands = exports.LanguageServer = void 0;
10
- require("array-flat-polyfill");
11
- const fastGlob = require("fast-glob");
12
10
  const path = require("path");
13
- const roku_deploy_1 = require("roku-deploy");
11
+ require("array-flat-polyfill");
14
12
  const node_1 = require("vscode-languageserver/node");
15
13
  const vscode_uri_1 = require("vscode-uri");
16
14
  const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
17
- const deferred_1 = require("./deferred");
18
- const ProgramBuilder_1 = require("./ProgramBuilder");
19
15
  const util_1 = require("./util");
20
- const Throttler_1 = require("./Throttler");
21
- const KeyedThrottler_1 = require("./KeyedThrottler");
22
16
  const DiagnosticCollection_1 = require("./DiagnosticCollection");
23
- const reflection_1 = require("./astUtils/reflection");
24
17
  const SemanticTokenUtils_1 = require("./SemanticTokenUtils");
25
- const BusyStatusTracker_1 = require("./BusyStatusTracker");
26
18
  const logging_1 = require("./logging");
19
+ const ignore_1 = require("ignore");
20
+ const micromatch = require("micromatch");
21
+ const PathFilterer_1 = require("./lsp/PathFilterer");
22
+ const ProjectManager_1 = require("./lsp/ProjectManager");
23
+ const fsExtra = require("fs-extra");
24
+ const WorkerThreadProject_1 = require("./lsp/worker/WorkerThreadProject");
25
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
26
+ const isEqual = require("lodash.isequal");
27
27
  class LanguageServer {
28
28
  constructor() {
29
- this.connection = undefined;
30
- this.projects = [];
31
29
  /**
32
- * The number of milliseconds that should be used for language server typing debouncing
30
+ * The language server protocol connection, used to send and receive all requests and responses
33
31
  */
34
- this.debounceTimeout = 150;
35
- /**
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
- * Also, they should only be created when the file is opened, and destroyed when the file is closed.
40
- */
41
- this.standaloneFileProjects = {};
32
+ this.connection = undefined;
42
33
  this.hasConfigurationCapability = false;
43
34
  /**
44
35
  * Indicates whether the client supports workspace folders
@@ -49,98 +40,79 @@ class LanguageServer {
49
40
  * The text document manager supports full document sync only
50
41
  */
51
42
  this.documents = new node_1.TextDocuments(vscode_languageserver_textdocument_1.TextDocument);
52
- this.keyedThrottler = new KeyedThrottler_1.KeyedThrottler(this.debounceTimeout);
53
- this.validateThrottler = new Throttler_1.Throttler(0);
54
- this.sendDiagnosticsThrottler = new Throttler_1.Throttler(0);
55
- this.boundValidateAll = this.validateAll.bind(this);
56
- this.busyStatusTracker = new BusyStatusTracker_1.BusyStatusTracker();
43
+ this.logger = (0, logging_1.createLogger)({
44
+ logLevel: logging_1.LogLevel.log
45
+ });
46
+ this.workspaceConfigsCache = new Map();
57
47
  this.busyStatusIndex = -1;
58
- /**
59
- * A unique project counter to help distinguish log entries in lsp mode
60
- */
61
- this.projectCounter = 0;
48
+ this.pathFiltererDisposables = [];
62
49
  this.diagnosticCollection = new DiagnosticCollection_1.DiagnosticCollection();
63
- }
64
- createConnection() {
65
- return (0, node_1.createConnection)(node_1.ProposedFeatures.all);
66
- }
67
- validateAllThrottled() {
68
- return this.validateThrottler.run(this.boundValidateAll);
50
+ (0, logging_1.setLspLoggerProps)();
51
+ //replace the workerPool logger with our own so logging info can be synced
52
+ WorkerThreadProject_1.workerPool.logger = this.logger.createLogger();
53
+ this.pathFilterer = new PathFilterer_1.PathFilterer({ logger: this.logger });
54
+ this.projectManager = new ProjectManager_1.ProjectManager({
55
+ pathFilterer: this.pathFilterer,
56
+ logger: this.logger.createLogger()
57
+ });
58
+ //anytime a project emits a collection of diagnostics, send them to the client
59
+ this.projectManager.on('diagnostics', (event) => {
60
+ this.logger.debug(`Received ${event.diagnostics.length} diagnostics from project ${event.project.projectNumber}`);
61
+ this.sendDiagnostics(event).catch(logAndIgnoreError);
62
+ });
63
+ // Send all open document changes whenever a project is activated. This is necessary because at project startup, the project loads files from disk
64
+ // and may not have the latest unsaved file changes. Any existing projects that already use these files will just ignore the changes
65
+ // because the file contents haven't changed.
66
+ this.projectManager.on('project-activate', (event) => {
67
+ var _a;
68
+ //keep logLevel in sync with the most verbose log level found across all projects
69
+ this.syncLogLevel().catch(logAndIgnoreError);
70
+ //resend all open document changes
71
+ const documents = [...this.documents.all()];
72
+ if (documents.length > 0) {
73
+ this.logger.log(`[${(_a = event.project) === null || _a === void 0 ? void 0 : _a.projectIdentifier}] loaded or changed. Resending all open document changes.`, documents.map(x => x.uri));
74
+ for (const document of this.documents.all()) {
75
+ this.onTextDocumentDidChangeContent({
76
+ document: document
77
+ }).catch(logAndIgnoreError);
78
+ }
79
+ }
80
+ });
81
+ this.projectManager.busyStatusTracker.on('active-runs-change', (event) => {
82
+ this.sendBusyStatus();
83
+ });
69
84
  }
70
85
  //run the server
71
86
  run() {
87
+ var _a;
72
88
  // Create a connection for the server. The connection uses Node's IPC as a transport.
73
- // Also include all preview / proposed LSP features.
74
- this.connection = this.createConnection();
75
- // Send the current status of the busyStatusTracker anytime it changes
76
- this.busyStatusTracker.on('change', (status) => {
77
- void this.sendBusyStatus(status);
78
- });
89
+ this.connection = this.establishConnection();
79
90
  //disable logger colors when running in LSP mode
80
91
  logging_1.logger.enableColor = false;
81
92
  //listen to all of the output log events and pipe them into the debug channel in the extension
82
93
  this.loggerSubscription = logging_1.logger.subscribe((message) => {
83
94
  this.connection.tracer.log(message.argsText);
84
95
  });
85
- this.connection.onInitialize(this.onInitialize.bind(this));
86
- this.connection.onInitialized(this.onInitialized.bind(this)); //eslint-disable-line
87
- this.connection.onDidChangeConfiguration(this.onDidChangeConfiguration.bind(this)); //eslint-disable-line
88
- this.connection.onDidChangeWatchedFiles(this.onDidChangeWatchedFiles.bind(this)); //eslint-disable-line
96
+ //bind all our on* methods that share the same name from connection
97
+ for (const name of Object.getOwnPropertyNames(LanguageServer.prototype)) {
98
+ if (/on+/.test(name) && typeof ((_a = this.connection) === null || _a === void 0 ? void 0 : _a[name]) === 'function') {
99
+ this.connection[name](this[name].bind(this));
100
+ }
101
+ }
102
+ //Register semantic token requests. TODO switch to a more specific connection function call once they actually add it
103
+ this.connection.onRequest(node_1.SemanticTokensRequest.method, this.onFullSemanticTokens.bind(this));
89
104
  // The content of a text document has changed. This event is emitted
90
105
  // when the text document is first opened, when its content has changed,
91
106
  // or when document is closed without saving (original contents are sent as a change)
92
107
  //
93
- this.documents.onDidChangeContent(this.validateTextDocument.bind(this));
108
+ this.documents.onDidChangeContent(this.onTextDocumentDidChangeContent.bind(this));
94
109
  //whenever a document gets closed
95
110
  this.documents.onDidClose(this.onDocumentClose.bind(this));
96
- // This handler provides the initial list of the completion items.
97
- this.connection.onCompletion(this.onCompletion.bind(this));
98
- // This handler resolves additional information for the item selected in
99
- // the completion list.
100
- this.connection.onCompletionResolve(this.onCompletionResolve.bind(this));
101
- this.connection.onHover(this.onHover.bind(this));
102
- this.connection.onExecuteCommand(this.onExecuteCommand.bind(this));
103
- this.connection.onDefinition(this.onDefinition.bind(this));
104
- this.connection.onDocumentSymbol(this.onDocumentSymbol.bind(this));
105
- this.connection.onWorkspaceSymbol(this.onWorkspaceSymbol.bind(this));
106
- this.connection.onSignatureHelp(this.onSignatureHelp.bind(this));
107
- this.connection.onReferences(this.onReferences.bind(this));
108
- this.connection.onCodeAction(this.onCodeAction.bind(this));
109
- //TODO switch to a more specific connection function call once they actually add it
110
- this.connection.onRequest(node_1.SemanticTokensRequest.method, this.onFullSemanticTokens.bind(this));
111
- /*
112
- this.connection.onDidOpenTextDocument((params) => {
113
- // A text document got opened in VSCode.
114
- // params.uri uniquely identifies the document. For documents stored on disk this is a file URI.
115
- // params.text the initial full content of the document.
116
- this.connection.console.log(`${params.textDocument.uri} opened.`);
117
- });
118
- this.connection.onDidChangeTextDocument((params) => {
119
- // The content of a text document did change in VSCode.
120
- // params.uri uniquely identifies the document.
121
- // params.contentChanges describe the content changes to the document.
122
- this.connection.console.log(`${params.textDocument.uri} changed: ${JSON.stringify(params.contentChanges)}`);
123
- });
124
- this.connection.onDidCloseTextDocument((params) => {
125
- // A text document got closed in VSCode.
126
- // params.uri uniquely identifies the document.
127
- this.connection.console.log(`${params.textDocument.uri} closed.`);
128
- });
129
- */
130
111
  // listen for open, change and close text document events
131
112
  this.documents.listen(this.connection);
132
113
  // Listen on the connection
133
114
  this.connection.listen();
134
115
  }
135
- async sendBusyStatus(status) {
136
- this.busyStatusIndex = ++this.busyStatusIndex <= 0 ? 0 : this.busyStatusIndex;
137
- await this.connection.sendNotification(NotificationName.busyStatus, {
138
- status: status,
139
- timestamp: Date.now(),
140
- index: this.busyStatusIndex,
141
- activeRuns: [...this.busyStatusTracker.activeRuns]
142
- });
143
- }
144
116
  /**
145
117
  * Called when the client starts initialization
146
118
  */
@@ -156,7 +128,7 @@ class LanguageServer {
156
128
  textDocumentSync: node_1.TextDocumentSyncKind.Full,
157
129
  // Tell the client that the server supports code completion
158
130
  completionProvider: {
159
- resolveProvider: true,
131
+ resolveProvider: false,
160
132
  //anytime the user types a period, auto-show the completion results
161
133
  triggerCharacters: ['.'],
162
134
  allCommitCharacters: ['.', '@']
@@ -184,146 +156,38 @@ class LanguageServer {
184
156
  }
185
157
  };
186
158
  }
187
- /**
188
- * Ask the client for the list of `files.exclude` patterns. Useful when determining if we should process a file
189
- */
190
- async getWorkspaceExcludeGlobs(workspaceFolder) {
191
- var _a;
192
- let config = {
193
- exclude: {}
194
- };
195
- //if supported, ask vscode for the `files.exclude` configuration
196
- if (this.hasConfigurationCapability) {
197
- //get any `files.exclude` globs to use to filter
198
- config = await this.connection.workspace.getConfiguration({
199
- scopeUri: workspaceFolder,
200
- section: 'files'
201
- });
202
- }
203
- return Object
204
- .keys((_a = config === null || config === void 0 ? void 0 : config.exclude) !== null && _a !== void 0 ? _a : {})
205
- .filter(x => { var _a; return (_a = config === null || config === void 0 ? void 0 : config.exclude) === null || _a === void 0 ? void 0 : _a[x]; })
206
- //vscode files.exclude patterns support ignoring folders without needing to add `**/*`. So for our purposes, we need to
207
- //append **/* to everything without a file extension or magic at the end
208
- .map(pattern => [
209
- //send the pattern as-is (this handles weird cases and exact file matches)
210
- pattern,
211
- //treat the pattern as a directory (no harm in doing this because if it's a file, the pattern will just never match anything)
212
- `${pattern}/**/*`
213
- ])
214
- .flat(1)
215
- .concat([
216
- //always ignore projects from node_modules
217
- '**/node_modules/**/*'
218
- ]);
219
- }
220
- /**
221
- * Scan the workspace for all `bsconfig.json` files. If at least one is found, then only folders who have bsconfig.json are returned.
222
- * If none are found, then the workspaceFolder itself is treated as a project
223
- */
224
- async getProjectPaths(workspaceFolder) {
225
- const excludes = (await this.getWorkspaceExcludeGlobs(workspaceFolder)).map(x => (0, util_1.standardizePath) `!${x}`);
226
- const files = await roku_deploy_1.rokuDeploy.getFilePaths([
227
- '**/bsconfig.json',
228
- //exclude all files found in `files.exclude`
229
- ...excludes
230
- ], workspaceFolder);
231
- //if we found at least one bsconfig.json, then ALL projects must have a bsconfig.json.
232
- if (files.length > 0) {
233
- return files.map(file => (0, util_1.standardizePath) `${path.dirname(file.src)}`);
234
- }
235
- //look for roku project folders
236
- const rokuLikeDirs = (await Promise.all(
237
- //find all folders containing a `manifest` file
238
- (await roku_deploy_1.rokuDeploy.getFilePaths([
239
- '**/manifest',
240
- ...excludes
241
- //is there at least one .bs|.brs file under the `/source` folder?
242
- ], workspaceFolder)).map(async (manifestEntry) => {
243
- const manifestDir = path.dirname(manifestEntry.src);
244
- const files = await roku_deploy_1.rokuDeploy.getFilePaths([
245
- 'source/**/*.{brs,bs}',
246
- ...excludes
247
- ], manifestDir);
248
- if (files.length > 0) {
249
- return manifestDir;
250
- }
251
- })
252
- //throw out nulls
253
- )).filter(x => !!x);
254
- if (rokuLikeDirs.length > 0) {
255
- return rokuLikeDirs;
256
- }
257
- //treat the workspace folder as a brightscript project itself
258
- return [workspaceFolder];
259
- }
260
- /**
261
- * Find all folders with bsconfig.json files in them, and treat each as a project.
262
- * Treat workspaces that don't have a bsconfig.json as a project.
263
- * Handle situations where bsconfig.json files were added or removed (to elevate/lower workspaceFolder projects accordingly)
264
- * Leave existing projects alone if they are not affected by these changes
265
- */
266
- async syncProjects() {
267
- const workspacePaths = await this.getWorkspacePaths();
268
- let projectPaths = (await Promise.all(workspacePaths.map(async (workspacePath) => {
269
- const projectPaths = await this.getProjectPaths(workspacePath);
270
- return projectPaths.map(projectPath => ({
271
- projectPath: projectPath,
272
- workspacePath: workspacePath
273
- }));
274
- }))).flat(1);
275
- //delete projects not represented in the list
276
- for (const project of this.getProjects()) {
277
- if (!projectPaths.find(x => x.projectPath === project.projectPath)) {
278
- this.removeProject(project);
279
- }
280
- }
281
- //exclude paths to projects we already have
282
- projectPaths = projectPaths.filter(x => {
283
- //only keep this project path if there's not a project with that path
284
- return !this.projects.find(project => project.projectPath === x.projectPath);
285
- });
286
- //dedupe by project path
287
- projectPaths = [
288
- ...projectPaths.reduce((acc, x) => acc.set(x.projectPath, x), new Map()).values()
289
- ];
290
- //create missing projects
291
- await Promise.all(projectPaths.map(x => this.createProject(x.projectPath, x.workspacePath)));
292
- //flush diagnostics
293
- await this.sendDiagnostics();
294
- }
295
- /**
296
- * Get all workspace paths from the client
297
- */
298
- async getWorkspacePaths() {
299
- var _a;
300
- let workspaceFolders = (_a = await this.connection.workspace.getWorkspaceFolders()) !== null && _a !== void 0 ? _a : [];
301
- return workspaceFolders.map((x) => {
302
- return util_1.util.uriToPath(x.uri);
303
- });
304
- }
305
159
  /**
306
160
  * Called when the client has finished initializing
307
161
  */
308
162
  async onInitialized() {
309
- let projectCreatedDeferred = new deferred_1.Deferred();
310
- this.initialProjectsCreated = projectCreatedDeferred.promise;
163
+ this.logger.log('onInitialized');
164
+ //cache a copy of all workspace configurations to use for comparison later
165
+ this.workspaceConfigsCache = new Map((await this.getWorkspaceConfigs()).map(x => [x.workspaceFolder, x]));
166
+ //set our logger to the most verbose logLevel found across any project
167
+ await this.syncLogLevel();
311
168
  try {
312
169
  if (this.hasConfigurationCapability) {
313
- // Register for all configuration changes.
314
- await this.connection.client.register(node_1.DidChangeConfigurationNotification.type, undefined);
170
+ // register for when the user changes workspace or user settings
171
+ await this.connection.client.register(node_1.DidChangeConfigurationNotification.type, {
172
+ //we only care about when these settings sections change
173
+ section: [
174
+ 'brightscript',
175
+ 'files'
176
+ ]
177
+ });
315
178
  }
179
+ //populate the path filterer with the client's include/exclude lists
180
+ await this.rebuildPathFilterer();
316
181
  await this.syncProjects();
317
182
  if (this.clientHasWorkspaceFolderCapability) {
183
+ //if the client changes their workspaces, we need to get our projects in sync
318
184
  this.connection.workspace.onDidChangeWorkspaceFolders(async (evt) => {
319
185
  await this.syncProjects();
320
186
  });
321
187
  }
322
- await this.waitAllProjectFirstRuns(false);
323
- projectCreatedDeferred.resolve();
324
188
  }
325
189
  catch (e) {
326
- await this.sendCriticalFailure(`Critical failure during BrighterScript language server startup.
190
+ this.sendCriticalFailure(`Critical failure during BrighterScript language server startup.
327
191
  Please file a github issue and include the contents of the 'BrighterScript Language Server' output channel.
328
192
 
329
193
  Error message: ${e.message}`);
@@ -331,367 +195,63 @@ class LanguageServer {
331
195
  }
332
196
  }
333
197
  /**
334
- * Send a critical failure notification to the client, which should show a notification of some kind
198
+ * Set our logLevel to the most verbose log level found across all projects and workspaces
335
199
  */
336
- async sendCriticalFailure(message) {
337
- await this.connection.sendNotification('critical-failure', message);
338
- }
339
- /**
340
- * Wait for all programs' first run to complete
341
- */
342
- async waitAllProjectFirstRuns(waitForFirstProject = true) {
343
- if (waitForFirstProject) {
344
- await this.initialProjectsCreated;
345
- }
346
- for (let project of this.getProjects()) {
347
- try {
348
- await project.firstRunPromise;
349
- }
350
- catch (e) {
351
- //the first run failed...that won't change unless we reload the workspace, so replace with resolved promise
352
- //so we don't show this error again
353
- project.firstRunPromise = Promise.resolve();
354
- await this.sendCriticalFailure(`BrighterScript language server failed to start: \n${e.message}`);
200
+ async syncLogLevel() {
201
+ var _a, _b;
202
+ /**
203
+ * helper to get the logLevel from a list of items and return the item and level (if found), or undefined if not
204
+ */
205
+ const getLogLevel = async (items, fetcher) => {
206
+ const logLevels = await Promise.all(items.map(async (item) => {
207
+ let value = await fetcher(item);
208
+ //force string values to lower case (so we can support things like 'log' or 'Log' or 'LOG')
209
+ if (typeof value === 'string') {
210
+ value = value.toLowerCase();
211
+ }
212
+ const logLevelNumeric = this.logger.getLogLevelNumeric(value);
213
+ if (typeof logLevelNumeric === 'number') {
214
+ return logLevelNumeric;
215
+ }
216
+ else {
217
+ return -1;
218
+ }
219
+ }));
220
+ let idx = logLevels.findIndex(x => x > -1);
221
+ if (idx > -1) {
222
+ const mostVerboseLogLevel = Math.max(...logLevels);
223
+ return {
224
+ logLevel: mostVerboseLogLevel,
225
+ logLevelText: this.logger.getLogLevelText(mostVerboseLogLevel),
226
+ //find the first item having the most verbose logLevel
227
+ item: items[logLevels.findIndex(x => x === mostVerboseLogLevel)]
228
+ };
355
229
  }
356
- }
357
- }
358
- /**
359
- * Event handler for when the program wants to load file contents.
360
- * anytime the program wants to load a file, check with our in-memory document cache first
361
- */
362
- documentFileResolver(srcPath) {
363
- let pathUri = util_1.util.pathToUri(srcPath);
364
- let document = this.documents.get(pathUri);
365
- if (document) {
366
- return document.getText();
367
- }
368
- }
369
- async getConfigFilePath(workspacePath) {
370
- let scopeUri;
371
- if (workspacePath.startsWith('file:')) {
372
- scopeUri = vscode_uri_1.URI.parse(workspacePath).toString();
373
- }
374
- else {
375
- scopeUri = util_1.util.pathToUri(workspacePath);
376
- }
377
- let config = {
378
- configFile: undefined
379
230
  };
380
- //if the client supports configuration, look for config group called "brightscript"
381
- if (this.hasConfigurationCapability) {
382
- config = await this.connection.workspace.getConfiguration({
383
- scopeUri: scopeUri,
384
- section: 'brightscript'
385
- });
386
- }
387
- let configFilePath;
388
- //if there's a setting, we need to find the file or show error if it can't be found
389
- if (config === null || config === void 0 ? void 0 : config.configFile) {
390
- configFilePath = path.resolve(workspacePath, config.configFile);
391
- if (await util_1.util.pathExists(configFilePath)) {
392
- return configFilePath;
393
- }
394
- else {
395
- await this.sendCriticalFailure(`Cannot find config file specified in user / workspace settings at '${configFilePath}'`);
396
- }
397
- }
398
- //default to config file path found in the root of the workspace
399
- configFilePath = path.resolve(workspacePath, 'bsconfig.json');
400
- if (await util_1.util.pathExists(configFilePath)) {
401
- return configFilePath;
402
- }
403
- //look for the deprecated `brsconfig.json` file
404
- configFilePath = path.resolve(workspacePath, 'brsconfig.json');
405
- if (await util_1.util.pathExists(configFilePath)) {
406
- return configFilePath;
407
- }
408
- //no config file could be found
409
- return undefined;
410
- }
411
- /**
412
- * @param projectPath path to the project
413
- * @param workspacePath path to the workspace in which all project should reside or are referenced by
414
- * @param projectNumber an optional project number to assign to the project. Used when reloading projects that should keep the same number
415
- */
416
- async createProject(projectPath, workspacePath = projectPath, projectNumber) {
417
- workspacePath !== null && workspacePath !== void 0 ? workspacePath : (workspacePath = projectPath);
418
- let project = this.projects.find((x) => x.projectPath === projectPath);
419
- //skip this project if we already have it
420
- if (project) {
231
+ const workspaces = await this.getWorkspaceConfigs();
232
+ let workspaceResult = await getLogLevel(workspaces, workspace => { var _a; return (_a = workspace === null || workspace === void 0 ? void 0 : workspace.languageServer) === null || _a === void 0 ? void 0 : _a.logLevel; });
233
+ if (workspaceResult) {
234
+ this.logger.info(`Setting global logLevel to '${workspaceResult.logLevelText}' based on configuration from workspace '${(_a = workspaceResult === null || workspaceResult === void 0 ? void 0 : workspaceResult.item) === null || _a === void 0 ? void 0 : _a.workspaceFolder}'`);
235
+ this.logger.logLevel = workspaceResult.logLevel;
421
236
  return;
422
237
  }
423
- let builder = new ProgramBuilder_1.ProgramBuilder();
424
- projectNumber !== null && projectNumber !== void 0 ? projectNumber : (projectNumber = this.projectCounter++);
425
- builder.logger.prefix = `[prj${projectNumber}]`;
426
- builder.logger.log(`Created project #${projectNumber} for: "${projectPath}"`);
427
- //flush diagnostics every time the program finishes validating
428
- builder.plugins.add({
429
- name: 'bsc-language-server',
430
- afterProgramValidate: () => {
431
- void this.sendDiagnostics();
432
- }
433
- });
434
- //prevent clearing the console on run...this isn't the CLI so we want to keep a full log of everything
435
- builder.allowConsoleClearing = false;
436
- //look for files in our in-memory cache before going to the file system
437
- builder.addFileResolver(this.documentFileResolver.bind(this));
438
- let configFilePath = await this.getConfigFilePath(projectPath);
439
- let cwd = projectPath;
440
- //if the config file exists, use it and its folder as cwd
441
- if (configFilePath && await util_1.util.pathExists(configFilePath)) {
442
- cwd = path.dirname(configFilePath);
443
- }
444
- else {
445
- //config file doesn't exist...let `brighterscript` resolve the default way
446
- configFilePath = undefined;
447
- }
448
- const firstRunDeferred = new deferred_1.Deferred();
449
- let newProject = {
450
- projectNumber: projectNumber,
451
- builder: builder,
452
- firstRunPromise: firstRunDeferred.promise,
453
- projectPath: projectPath,
454
- workspacePath: workspacePath,
455
- isFirstRunComplete: false,
456
- isFirstRunSuccessful: false,
457
- configFilePath: configFilePath,
458
- isStandaloneFileProject: false
459
- };
460
- this.projects.push(newProject);
461
- try {
462
- await builder.run({
463
- cwd: cwd,
464
- project: configFilePath,
465
- watch: false,
466
- createPackage: false,
467
- deploy: false,
468
- copyToStaging: false,
469
- showDiagnosticsInConsole: false
470
- });
471
- newProject.isFirstRunComplete = true;
472
- newProject.isFirstRunSuccessful = true;
473
- firstRunDeferred.resolve();
474
- }
475
- catch (e) {
476
- builder.logger.error(e);
477
- firstRunDeferred.reject(e);
478
- newProject.isFirstRunComplete = true;
479
- newProject.isFirstRunSuccessful = false;
480
- }
481
- }
482
- async createStandaloneFileProject(srcPath) {
483
- //skip this workspace if we already have it
484
- if (this.standaloneFileProjects[srcPath]) {
485
- return this.standaloneFileProjects[srcPath];
486
- }
487
- let builder = new ProgramBuilder_1.ProgramBuilder();
488
- //prevent clearing the console on run...this isn't the CLI so we want to keep a full log of everything
489
- builder.allowConsoleClearing = false;
490
- //look for files in our in-memory cache before going to the file system
491
- builder.addFileResolver(this.documentFileResolver.bind(this));
492
- //get the path to the directory where this file resides
493
- let cwd = path.dirname(srcPath);
494
- //get the closest config file and use most of the settings from that
495
- let configFilePath = await util_1.util.findClosestConfigFile(srcPath);
496
- let project = {};
497
- if (configFilePath) {
498
- project = util_1.util.normalizeAndResolveConfig({ project: configFilePath });
499
- }
500
- //override the rootDir and files array
501
- project.rootDir = cwd;
502
- project.files = [{
503
- src: srcPath,
504
- dest: path.basename(srcPath)
505
- }];
506
- let firstRunPromise = builder.run(Object.assign(Object.assign({}, project), { cwd: cwd, project: configFilePath, watch: false, createPackage: false, deploy: false, copyToStaging: false, diagnosticFilters: [
507
- //hide the "file not referenced by any other file" error..that's expected in a standalone file.
508
- 1013
509
- ] })).catch((err) => {
510
- console.error(err);
511
- });
512
- let newProject = {
513
- projectNumber: this.projectCounter++,
514
- builder: builder,
515
- firstRunPromise: firstRunPromise,
516
- projectPath: srcPath,
517
- workspacePath: srcPath,
518
- isFirstRunComplete: false,
519
- isFirstRunSuccessful: false,
520
- configFilePath: configFilePath,
521
- isStandaloneFileProject: true
522
- };
523
- this.standaloneFileProjects[srcPath] = newProject;
524
- await firstRunPromise.then(() => {
525
- newProject.isFirstRunComplete = true;
526
- newProject.isFirstRunSuccessful = true;
527
- }).catch(() => {
528
- newProject.isFirstRunComplete = true;
529
- newProject.isFirstRunSuccessful = false;
530
- });
531
- return newProject;
532
- }
533
- getProjects() {
534
- let projects = this.projects.slice();
535
- for (let key in this.standaloneFileProjects) {
536
- projects.push(this.standaloneFileProjects[key]);
537
- }
538
- return projects;
539
- }
540
- /**
541
- * Provide a list of completion items based on the current cursor position
542
- */
543
- async onCompletion(params) {
544
- //ensure programs are initialized
545
- await this.waitAllProjectFirstRuns();
546
- let filePath = util_1.util.uriToPath(params.textDocument.uri);
547
- //wait until the file has settled
548
- await this.keyedThrottler.onIdleOnce(filePath, true);
549
- // make sure validation is complete
550
- await this.validateAllThrottled();
551
- //wait for the validation cycle to settle
552
- await this.onValidateSettled();
553
- let completions = this
554
- .getProjects()
555
- .flatMap(workspace => workspace.builder.program.getCompletions(filePath, params.position));
556
- //only send one completion if name and type are the same
557
- let completionsMap = new Map();
558
- for (let completion of completions) {
559
- completion.commitCharacters = ['.'];
560
- let key = `${completion.sortText}-${completion.label}-${completion.kind}`;
561
- completionsMap.set(key, completion);
562
- }
563
- return [...completionsMap.values()];
564
- }
565
- /**
566
- * Provide a full completion item from the selection
567
- */
568
- onCompletionResolve(item) {
569
- if (item.data === 1) {
570
- item.detail = 'TypeScript details';
571
- item.documentation = 'TypeScript documentation';
572
- }
573
- else if (item.data === 2) {
574
- item.detail = 'JavaScript details';
575
- item.documentation = 'JavaScript documentation';
576
- }
577
- return item;
578
- }
579
- async onCodeAction(params) {
580
- var _a;
581
- //ensure programs are initialized
582
- await this.waitAllProjectFirstRuns();
583
- let srcPath = util_1.util.uriToPath(params.textDocument.uri);
584
- //wait until the file has settled
585
- await this.keyedThrottler.onIdleOnce(srcPath, true);
586
- const codeActions = this
587
- .getProjects()
588
- //skip programs that don't have this file
589
- .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); })
590
- .flatMap(workspace => workspace.builder.program.getCodeActions(srcPath, params.range));
591
- //clone the diagnostics for each code action, since certain diagnostics can have circular reference properties that kill the language server if serialized
592
- for (const codeAction of codeActions) {
593
- if (codeAction.diagnostics) {
594
- codeAction.diagnostics = (_a = codeAction.diagnostics) === null || _a === void 0 ? void 0 : _a.map(x => util_1.util.toDiagnostic(x, params.textDocument.uri));
595
- }
596
- }
597
- return codeActions;
598
- }
599
- /**
600
- * Remove a project from the language server
601
- */
602
- removeProject(project) {
603
- var _a;
604
- const idx = this.projects.indexOf(project);
605
- if (idx > -1) {
606
- this.projects.splice(idx, 1);
607
- }
608
- (_a = project === null || project === void 0 ? void 0 : project.builder) === null || _a === void 0 ? void 0 : _a.dispose();
609
- }
610
- /**
611
- * Reload each of the specified workspaces
612
- */
613
- async reloadProjects(projects) {
614
- await Promise.all(projects.map(async (project) => {
615
- //ensure the workspace has finished starting up
616
- try {
617
- await project.firstRunPromise;
618
- }
619
- catch (e) { }
620
- //handle standard workspace
621
- if (project.isStandaloneFileProject === false) {
622
- this.removeProject(project);
623
- //create a new workspace/brs program
624
- await this.createProject(project.projectPath, project.workspacePath, project.projectNumber);
625
- //handle temp workspace
626
- }
627
- else {
628
- project.builder.dispose();
629
- delete this.standaloneFileProjects[project.projectPath];
630
- await this.createStandaloneFileProject(project.projectPath);
631
- }
632
- }));
633
- if (projects.length > 0) {
634
- //wait for all of the programs to finish starting up
635
- await this.waitAllProjectFirstRuns();
636
- // valdiate all workspaces
637
- this.validateAllThrottled(); //eslint-disable-line
638
- }
639
- }
640
- getRootDir(workspace) {
641
- var _a, _b, _c;
642
- let options = (_b = (_a = workspace === null || workspace === void 0 ? void 0 : workspace.builder) === null || _a === void 0 ? void 0 : _a.program) === null || _b === void 0 ? void 0 : _b.options;
643
- return (_c = options === null || options === void 0 ? void 0 : options.rootDir) !== null && _c !== void 0 ? _c : options === null || options === void 0 ? void 0 : options.cwd;
644
- }
645
- /**
646
- * Sometimes users will alter their bsconfig files array, and will include standalone files.
647
- * If this is the case, those standalone workspaces should be removed because the file was
648
- * included in an actual program now.
649
- *
650
- * Sometimes files that used to be included are now excluded, so those open files need to be re-processed as standalone
651
- */
652
- async synchronizeStandaloneProjects() {
653
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
654
- //remove standalone workspaces that are now included in projects
655
- for (let standaloneFilePath in this.standaloneFileProjects) {
656
- let standaloneProject = this.standaloneFileProjects[standaloneFilePath];
657
- for (let project of this.projects) {
658
- await standaloneProject.firstRunPromise;
659
- 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));
660
- //destroy this standalone workspace because the file has now been included in an actual workspace,
661
- //or if the workspace wants the file
662
- 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) {
663
- standaloneProject.builder.dispose();
664
- delete this.standaloneFileProjects[standaloneFilePath];
665
- }
666
- }
667
- }
668
- //create standalone projects for open files that no longer have a project
669
- let textDocuments = this.documents.all();
670
- outer: for (let textDocument of textDocuments) {
671
- let filePath = vscode_uri_1.URI.parse(textDocument.uri).fsPath;
672
- for (let project of this.getProjects()) {
673
- 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));
674
- //if this project has the file, or it wants the file, do NOT make a standaloneProject for this file
675
- 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) {
676
- continue outer;
677
- }
678
- }
679
- //if we got here, no workspace has this file, so make a standalone file workspace
680
- let project = await this.createStandaloneFileProject(filePath);
681
- await project.firstRunPromise;
238
+ let projectResult = await getLogLevel(this.projectManager.projects, (project) => project.logger.logLevel);
239
+ if (projectResult) {
240
+ this.logger.info(`Setting global logLevel to '${projectResult.logLevelText}' based on project #${(_b = projectResult === null || projectResult === void 0 ? void 0 : projectResult.item) === null || _b === void 0 ? void 0 : _b.projectNumber}`);
241
+ this.logger.logLevel = projectResult.logLevel;
242
+ return;
682
243
  }
244
+ //use a default level if no other level was found
245
+ this.logger.logLevel = logging_1.LogLevel.log;
683
246
  }
684
- async onDidChangeConfiguration() {
685
- if (this.hasConfigurationCapability) {
686
- //if the user changes any config value, just mass-reload all projects
687
- await this.reloadProjects(this.getProjects());
688
- // Reset all cached document settings
689
- }
690
- else {
691
- // this.globalSettings = <ExampleSettings>(
692
- // (change.settings.languageServerExample || this.defaultSettings)
693
- // );
694
- }
247
+ async onTextDocumentDidChangeContent(event) {
248
+ this.logger.debug('onTextDocumentDidChangeContent', event.document.uri);
249
+ await this.projectManager.handleFileChanges([{
250
+ srcPath: vscode_uri_1.URI.parse(event.document.uri).fsPath,
251
+ type: node_1.FileChangeType.Changed,
252
+ fileContents: event.document.getText(),
253
+ allowStandaloneProject: true
254
+ }]);
695
255
  }
696
256
  /**
697
257
  * Called when watched files changed (add/change/delete).
@@ -700,446 +260,353 @@ class LanguageServer {
700
260
  * file types are watched (.brs,.bs,.xml,manifest, and any json/text/image files)
701
261
  */
702
262
  async onDidChangeWatchedFiles(params) {
703
- //ensure programs are initialized
704
- await this.waitAllProjectFirstRuns();
705
- let projects = this.getProjects();
706
- //convert all file paths to absolute paths
707
- let changes = params.changes.map(x => {
708
- return {
709
- type: x.type,
710
- srcPath: (0, util_1.standardizePath) `${vscode_uri_1.URI.parse(x.uri).fsPath}`
711
- };
263
+ const workspacePaths = (await this.connection.workspace.getWorkspaceFolders()).map(x => util_1.util.uriToPath(x.uri));
264
+ let changes = params.changes
265
+ .map(x => ({
266
+ srcPath: util_1.util.uriToPath(x.uri),
267
+ type: x.type,
268
+ //if this is an open document, allow this file to be loaded in a standalone project (if applicable)
269
+ allowStandaloneProject: this.documents.get(x.uri) !== undefined
270
+ }))
271
+ //exclude all explicit top-level workspace folder paths (to fix a weird macos fs watcher bug that emits events for the workspace folder itself)
272
+ .filter(x => !workspacePaths.includes(x.srcPath));
273
+ this.logger.debug('onDidChangeWatchedFiles', changes);
274
+ //if the client changed any files containing include/exclude patterns, rebuild the path filterer before processing these changes
275
+ if (micromatch.some(changes.map(x => x.srcPath), [
276
+ '**/.gitignore',
277
+ '**/.vscode/settings.json',
278
+ '**/*bsconfig*.json'
279
+ ], {
280
+ dot: true
281
+ })) {
282
+ await this.rebuildPathFilterer();
283
+ }
284
+ //handle the file changes
285
+ await this.projectManager.handleFileChanges(changes);
286
+ }
287
+ async onDocumentClose(event) {
288
+ this.logger.debug('onDocumentClose', event.document.uri);
289
+ await this.projectManager.handleFileClose({
290
+ srcPath: util_1.util.uriToPath(event.document.uri)
712
291
  });
713
- let keys = changes.map(x => x.srcPath);
714
- //filter the list of changes to only the ones that made it through the debounce unscathed
715
- changes = changes.filter(x => keys.includes(x.srcPath));
716
- //if we have changes to work with
717
- if (changes.length > 0) {
718
- //if any bsconfig files were added or deleted, re-sync all projects instead of the more specific approach below
719
- if (changes.find(x => (x.type === node_1.FileChangeType.Created || x.type === node_1.FileChangeType.Deleted) && path.basename(x.srcPath).toLowerCase() === 'bsconfig.json')) {
720
- return this.syncProjects();
721
- }
722
- //reload any workspace whose bsconfig.json file has changed
723
- {
724
- let projectsToReload = [];
725
- //get the file paths as a string array
726
- let filePaths = changes.map((x) => x.srcPath);
727
- for (let project of projects) {
728
- if (project.configFilePath && filePaths.includes(project.configFilePath)) {
729
- projectsToReload.push(project);
730
- }
731
- }
732
- if (projectsToReload.length > 0) {
733
- //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
734
- //reload any projects that need to be reloaded
735
- await this.reloadProjects(projectsToReload);
736
- }
737
- //reassign `projects` to the non-reloaded projects
738
- projects = projects.filter(x => !projectsToReload.includes(x));
739
- }
740
- //convert created folders into a list of files of their contents
741
- const directoryChanges = changes
742
- //get only creation items
743
- .filter(change => change.type === node_1.FileChangeType.Created)
744
- //keep only the directories
745
- .filter(change => util_1.util.isDirectorySync(change.srcPath));
746
- //remove the created directories from the changes array (we will add back each of their files next)
747
- changes = changes.filter(x => !directoryChanges.includes(x));
748
- //look up every file in each of the newly added directories
749
- const newFileChanges = directoryChanges
750
- //take just the path
751
- .map(x => x.srcPath)
752
- //exclude the roku deploy staging folder
753
- .filter(dirPath => !dirPath.includes('.roku-deploy-staging'))
754
- //get the files for each folder recursively
755
- .flatMap(dirPath => {
756
- //look up all files
757
- let files = fastGlob.sync('**/*', {
758
- absolute: true,
759
- cwd: roku_deploy_1.util.toForwardSlashes(dirPath)
760
- });
761
- return files.map(x => {
762
- return {
763
- type: node_1.FileChangeType.Created,
764
- srcPath: (0, util_1.standardizePath) `${x}`
765
- };
766
- });
767
- });
768
- //add the new file changes to the changes array.
769
- changes.push(...newFileChanges);
770
- //give every workspace the chance to handle file changes
771
- await Promise.all(projects.map((project) => this.handleFileChanges(project, changes)));
772
- }
773
292
  }
774
293
  /**
775
- * This only operates on files that match the specified files globs, so it is safe to throw
776
- * any file changes you receive with no unexpected side-effects
294
+ * Provide a list of completion items based on the current cursor position
777
295
  */
778
- async handleFileChanges(project, changes) {
779
- //this loop assumes paths are both file paths and folder paths, which eliminates the need to detect.
780
- //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
781
- await Promise.all(changes.map(async (change) => {
782
- await this.keyedThrottler.run(change.srcPath, async () => {
783
- if (await this.handleFileChange(project, change)) {
784
- await this.validateAllThrottled();
785
- }
786
- });
787
- }));
296
+ async onCompletion(params, cancellationToken, workDoneProgress, resultProgress) {
297
+ this.logger.debug('onCompletion', params, cancellationToken);
298
+ const srcPath = util_1.util.uriToPath(params.textDocument.uri);
299
+ const completions = await this.projectManager.getCompletions({
300
+ srcPath: srcPath,
301
+ position: params.position,
302
+ cancellationToken: cancellationToken
303
+ });
304
+ return completions;
788
305
  }
789
306
  /**
790
- * This only operates on files that match the specified files globs, so it is safe to throw
791
- * any file changes you receive with no unexpected side-effects
792
- * @returns true if the file was handled by this project, false if it was not
307
+ * Get a list of workspaces, and their configurations.
308
+ * Get only the settings for the workspace that are relevant to the language server. We do this so we can cache this object for use in change detection in the future.
793
309
  */
794
- async handleFileChange(project, change) {
795
- const { program, options, rootDir } = project.builder;
796
- //deleted
797
- if (change.type === node_1.FileChangeType.Deleted) {
798
- //try to act on this path as a directory
799
- project.builder.removeFilesInFolder(change.srcPath);
800
- //if this is a file loaded in the program, remove it
801
- if (program.hasFile(change.srcPath)) {
802
- program.removeFile(change.srcPath);
803
- return true;
804
- }
805
- else {
806
- return false;
807
- }
808
- //created
809
- }
810
- else if (change.type === node_1.FileChangeType.Created) {
811
- // thanks to `onDidChangeWatchedFiles`, we can safely assume that all "Created" changes are file paths, (not directories)
812
- //get the dest path for this file.
813
- let destPath = roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir);
814
- //if we got a dest path, then the program wants this file
815
- if (destPath) {
816
- program.setFile({
817
- src: change.srcPath,
818
- dest: roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
819
- }, await project.builder.getFileContents(change.srcPath));
820
- return true;
821
- }
822
- else {
823
- //no dest path means the program doesn't want this file
824
- return false;
825
- }
826
- //changed
827
- }
828
- else if (program.hasFile(change.srcPath)) {
829
- //sometimes "changed" events are emitted on files that were actually deleted,
830
- //so determine file existance and act accordingly
831
- if (await util_1.util.pathExists(change.srcPath)) {
832
- program.setFile({
833
- src: change.srcPath,
834
- dest: roku_deploy_1.rokuDeploy.getDestPath(change.srcPath, options.files, rootDir)
835
- }, await project.builder.getFileContents(change.srcPath));
836
- }
837
- else {
838
- program.removeFile(change.srcPath);
839
- }
840
- return true;
841
- }
842
- }
843
- async onHover(params) {
310
+ async getWorkspaceConfigs() {
844
311
  var _a;
845
- //ensure programs are initialized
846
- await this.waitAllProjectFirstRuns();
847
- const srcPath = util_1.util.uriToPath(params.textDocument.uri);
848
- let projects = this.getProjects();
849
- let hovers = projects
850
- //get hovers from all projects
851
- .map((x) => x.builder.program.getHover(srcPath, params.position))
852
- //flatten to a single list
853
- .flat();
854
- const contents = [
855
- ...(hovers !== null && hovers !== void 0 ? hovers : [])
856
- //pull all hover contents out into a flag array of strings
857
- .map(x => {
858
- return Array.isArray(x === null || x === void 0 ? void 0 : x.contents) ? x === null || x === void 0 ? void 0 : x.contents : [x === null || x === void 0 ? void 0 : x.contents];
859
- }).flat()
860
- //remove nulls
861
- .filter(x => !!x)
862
- //dedupe hovers across all projects
863
- .reduce((set, content) => set.add(content), new Set()).values()
864
- ];
865
- if (contents.length > 0) {
866
- let hover = {
867
- //use the range from the first hover
868
- range: (_a = hovers[0]) === null || _a === void 0 ? void 0 : _a.range,
869
- //the contents of all hovers
870
- contents: contents
871
- };
872
- return hover;
873
- }
874
- }
875
- async onDocumentClose(event) {
876
- const { document } = event;
877
- let filePath = vscode_uri_1.URI.parse(document.uri).fsPath;
878
- let standaloneFileProject = this.standaloneFileProjects[filePath];
879
- //if this was a temp file, close it
880
- if (standaloneFileProject) {
881
- await standaloneFileProject.firstRunPromise;
882
- standaloneFileProject.builder.dispose();
883
- delete this.standaloneFileProjects[filePath];
884
- await this.sendDiagnostics();
885
- }
886
- }
887
- async validateTextDocument(event) {
888
- const { document } = event;
889
- //ensure programs are initialized
890
- await this.waitAllProjectFirstRuns();
891
- let filePath = vscode_uri_1.URI.parse(document.uri).fsPath;
892
- try {
893
- //throttle file processing. first call is run immediately, and then the last call is processed.
894
- await this.keyedThrottler.run(filePath, () => {
895
- var _a;
896
- let documentText = document.getText();
897
- for (const project of this.getProjects()) {
898
- //only add or replace existing files. All of the files in the project should
899
- //have already been loaded by other means
900
- if (project.builder.program.hasFile(filePath)) {
901
- let rootDir = (_a = project.builder.program.options.rootDir) !== null && _a !== void 0 ? _a : project.builder.program.options.cwd;
902
- let dest = roku_deploy_1.rokuDeploy.getDestPath(filePath, project.builder.program.options.files, rootDir);
903
- project.builder.program.setFile({
904
- src: filePath,
905
- dest: dest
906
- }, documentText);
907
- }
312
+ //get all workspace folders (we'll use these to get settings)
313
+ let workspaces = await Promise.all(((_a = await this.connection.workspace.getWorkspaceFolders()) !== null && _a !== void 0 ? _a : []).map(async (x) => {
314
+ var _a, _b, _c;
315
+ const workspaceFolder = util_1.util.uriToPath(x.uri);
316
+ const brightscriptConfig = await this.getClientConfiguration(x.uri, 'brightscript');
317
+ return {
318
+ workspaceFolder: workspaceFolder,
319
+ excludePatterns: await this.getWorkspaceExcludeGlobs(workspaceFolder),
320
+ bsconfigPath: brightscriptConfig.configFile,
321
+ languageServer: {
322
+ enableThreading: (_b = (_a = brightscriptConfig.languageServer) === null || _a === void 0 ? void 0 : _a.enableThreading) !== null && _b !== void 0 ? _b : LanguageServer.enableThreadingDefault,
323
+ logLevel: (_c = brightscriptConfig === null || brightscriptConfig === void 0 ? void 0 : brightscriptConfig.languageServer) === null || _c === void 0 ? void 0 : _c.logLevel
908
324
  }
909
- });
910
- // validate all projects
911
- await this.validateAllThrottled();
912
- }
913
- catch (e) {
914
- await this.sendCriticalFailure(`Critical error parsing/validating ${filePath}: ${e.message}`);
325
+ };
326
+ }));
327
+ return workspaces;
328
+ }
329
+ async onDidChangeConfiguration(args) {
330
+ this.logger.log('onDidChangeConfiguration', 'Reloading all projects');
331
+ const configs = new Map((await this.getWorkspaceConfigs()).map(x => [x.workspaceFolder, x]));
332
+ //find any changed configs. This includes newly created workspaces, deleted workspaces, etc.
333
+ //TODO: enhance this to only reload specific projects, depending on the change
334
+ if (!isEqual(configs, this.workspaceConfigsCache)) {
335
+ //now that we've processed any config diffs, update the cached copy of them
336
+ this.workspaceConfigsCache = configs;
337
+ //if configuration changed, rebuild the path filterer
338
+ await this.rebuildPathFilterer();
339
+ //if the user changes any user/workspace config settings, just mass-reload all projects
340
+ await this.syncProjects(true);
915
341
  }
916
342
  }
917
- async validateAll() {
918
- var _a;
919
- try {
920
- //synchronize parsing for open files that were included/excluded from projects
921
- await this.synchronizeStandaloneProjects();
922
- let projects = this.getProjects();
923
- //validate all programs
924
- await Promise.all(projects.map((project) => {
925
- project.builder.program.validate();
926
- return project;
927
- }));
928
- }
929
- catch (e) {
930
- this.connection.console.error(e);
931
- await this.sendCriticalFailure(`Critical error validating project: ${e.message}${(_a = e.stack) !== null && _a !== void 0 ? _a : ''}`);
932
- }
343
+ async onHover(params) {
344
+ this.logger.debug('onHover', params);
345
+ const srcPath = util_1.util.uriToPath(params.textDocument.uri);
346
+ const result = await this.projectManager.getHover({ srcPath: srcPath, position: params.position });
347
+ return result;
933
348
  }
934
349
  async onWorkspaceSymbol(params) {
935
- await this.waitAllProjectFirstRuns();
936
- const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
937
- return project.builder.program.getWorkspaceSymbols();
938
- })), c => c);
939
- // Remove duplicates
940
- const allSymbols = Object.values(results.reduce((map, symbol) => {
941
- const key = symbol.location.uri + symbol.name;
942
- map[key] = symbol;
943
- return map;
944
- }, {}));
945
- return allSymbols;
350
+ this.logger.debug('onWorkspaceSymbol', params);
351
+ const result = await this.projectManager.getWorkspaceSymbol();
352
+ return result;
946
353
  }
947
354
  async onDocumentSymbol(params) {
948
- await this.waitAllProjectFirstRuns();
949
- await this.keyedThrottler.onIdleOnce(util_1.util.uriToPath(params.textDocument.uri), true);
355
+ this.logger.debug('onDocumentSymbol', params);
950
356
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
951
- for (const project of this.getProjects()) {
952
- const file = project.builder.program.getFile(srcPath);
953
- if ((0, reflection_1.isBrsFile)(file)) {
954
- return file.getDocumentSymbols();
955
- }
956
- }
357
+ const result = await this.projectManager.getDocumentSymbol({ srcPath: srcPath });
358
+ return result;
957
359
  }
958
360
  async onDefinition(params) {
959
- await this.waitAllProjectFirstRuns();
361
+ this.logger.debug('onDefinition', params);
960
362
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
961
- const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
962
- return project.builder.program.getDefinition(srcPath, params.position);
963
- })), c => c);
964
- return results;
363
+ const result = this.projectManager.getDefinition({ srcPath: srcPath, position: params.position });
364
+ return result;
965
365
  }
966
366
  async onSignatureHelp(params) {
967
- var _a, _b, _c;
968
- await this.waitAllProjectFirstRuns();
969
- const filepath = util_1.util.uriToPath(params.textDocument.uri);
970
- await this.keyedThrottler.onIdleOnce(filepath, true);
971
- try {
972
- const signatures = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => project.builder.program.getSignatureHelp(filepath, params.position))), c => c);
973
- const activeSignature = signatures.length > 0 ? 0 : null;
974
- const activeParameter = activeSignature !== null ? (_a = signatures[activeSignature]) === null || _a === void 0 ? void 0 : _a.index : null;
975
- let results = {
976
- signatures: signatures.map((s) => s.signature),
977
- activeSignature: activeSignature,
978
- activeParameter: activeParameter
979
- };
980
- return results;
367
+ this.logger.debug('onSignatureHelp', params);
368
+ const srcPath = util_1.util.uriToPath(params.textDocument.uri);
369
+ const result = await this.projectManager.getSignatureHelp({ srcPath: srcPath, position: params.position });
370
+ if (result) {
371
+ return result;
981
372
  }
982
- catch (e) {
983
- this.connection.console.error(`error in onSignatureHelp: ${(_c = (_b = e.stack) !== null && _b !== void 0 ? _b : e.message) !== null && _c !== void 0 ? _c : e}`);
373
+ else {
984
374
  return {
985
375
  signatures: [],
986
- activeSignature: 0,
987
- activeParameter: 0
376
+ activeSignature: null,
377
+ activeParameter: null
988
378
  };
989
379
  }
990
380
  }
991
381
  async onReferences(params) {
992
- await this.waitAllProjectFirstRuns();
993
- const position = params.position;
382
+ this.logger.debug('onReferences', params);
994
383
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
995
- const results = util_1.util.flatMap(await Promise.all(this.getProjects().map(project => {
996
- return project.builder.program.getReferences(srcPath, position);
997
- })), c => c !== null && c !== void 0 ? c : []);
998
- return results.filter((r) => r);
999
- }
1000
- onValidateSettled() {
1001
- return Promise.all([
1002
- //wait for the validator to start running (or timeout if it never did)
1003
- this.validateThrottler.onRunOnce(100),
1004
- //wait for the validator to stop running (or resolve immediately if it's already idle)
1005
- this.validateThrottler.onIdleOnce(true)
1006
- ]);
384
+ const result = await this.projectManager.getReferences({ srcPath: srcPath, position: params.position });
385
+ return result !== null && result !== void 0 ? result : [];
1007
386
  }
1008
387
  async onFullSemanticTokens(params) {
1009
- await this.waitAllProjectFirstRuns();
1010
- //wait for the file to settle (in case there are multiple file changes in quick succession)
1011
- await this.keyedThrottler.onIdleOnce(util_1.util.uriToPath(params.textDocument.uri), true);
1012
- // make sure validation is complete
1013
- await this.validateAllThrottled();
1014
- //wait for the validation cycle to settle
1015
- await this.onValidateSettled();
388
+ this.logger.debug('onFullSemanticTokens', params);
1016
389
  const srcPath = util_1.util.uriToPath(params.textDocument.uri);
1017
- for (const project of this.projects) {
1018
- //find the first program that has this file, since it would be incredibly inefficient to generate semantic tokens for the same file multiple times.
1019
- if (project.builder.program.hasFile(srcPath)) {
1020
- let semanticTokens = project.builder.program.getSemanticTokens(srcPath);
1021
- if (semanticTokens !== undefined) {
1022
- return {
1023
- data: (0, SemanticTokenUtils_1.encodeSemanticTokens)(semanticTokens)
1024
- };
1025
- }
1026
- }
1027
- }
390
+ const result = await this.projectManager.getSemanticTokens({ srcPath: srcPath });
391
+ return {
392
+ data: (0, SemanticTokenUtils_1.encodeSemanticTokens)(result)
393
+ };
1028
394
  }
1029
- async sendDiagnostics() {
1030
- await this.sendDiagnosticsThrottler.run(async () => {
1031
- //wait for all programs to finish running. This ensures the `Program` exists.
1032
- await Promise.all(this.projects.map(x => x.firstRunPromise));
1033
- //Get only the changes to diagnostics since the last time we sent them to the client
1034
- const patch = this.diagnosticCollection.getPatch(this.projects);
1035
- for (let fileUri in patch) {
1036
- const diagnostics = patch[fileUri].map(d => util_1.util.toDiagnostic(d, fileUri));
1037
- await this.connection.sendDiagnostics({
1038
- uri: fileUri,
1039
- diagnostics: diagnostics
1040
- });
1041
- }
1042
- });
395
+ async onCodeAction(params) {
396
+ this.logger.debug('onCodeAction', params);
397
+ const srcPath = util_1.util.uriToPath(params.textDocument.uri);
398
+ const result = await this.projectManager.getCodeActions({ srcPath: srcPath, range: params.range });
399
+ return result;
1043
400
  }
1044
401
  async onExecuteCommand(params) {
1045
- await this.waitAllProjectFirstRuns();
402
+ this.logger.debug('onExecuteCommand', params);
1046
403
  if (params.command === CustomCommands.TranspileFile) {
1047
- const result = await this.transpileFile(params.arguments[0]);
404
+ const args = {
405
+ srcPath: params.arguments[0]
406
+ };
407
+ const result = await this.projectManager.transpileFile(args);
1048
408
  //back-compat: include `pathAbsolute` property so older vscode versions still work
1049
409
  result.pathAbsolute = result.srcPath;
1050
410
  return result;
1051
411
  }
1052
412
  }
1053
- async transpileFile(srcPath) {
1054
- //wait all program first runs
1055
- await this.waitAllProjectFirstRuns();
1056
- //find the first project that has this file
1057
- for (let project of this.getProjects()) {
1058
- if (project.builder.program.hasFile(srcPath)) {
1059
- return project.builder.program.getTranspiledFileContents(srcPath);
413
+ /**
414
+ * Establish a connection to the client if not already connected
415
+ */
416
+ establishConnection() {
417
+ if (!this.connection) {
418
+ this.connection = (0, node_1.createConnection)(node_1.ProposedFeatures.all);
419
+ }
420
+ return this.connection;
421
+ }
422
+ /**
423
+ * Send a new busy status notification to the client based on the current busy status
424
+ */
425
+ sendBusyStatus() {
426
+ var _a;
427
+ this.busyStatusIndex = ++this.busyStatusIndex <= 0 ? 0 : this.busyStatusIndex;
428
+ (_a = this.connection.sendNotification(NotificationName.busyStatus, {
429
+ status: this.projectManager.busyStatusTracker.status,
430
+ timestamp: Date.now(),
431
+ index: this.busyStatusIndex,
432
+ activeRuns: [
433
+ //extract only specific information from the active run so we know what's going on
434
+ ...this.projectManager.busyStatusTracker.activeRuns.map(x => {
435
+ var _a;
436
+ return ({
437
+ scope: (_a = x.scope) === null || _a === void 0 ? void 0 : _a.projectIdentifier,
438
+ label: x.label,
439
+ startTime: x.startTime.getTime()
440
+ });
441
+ })
442
+ ]
443
+ })) === null || _a === void 0 ? void 0 : _a.catch(logAndIgnoreError);
444
+ }
445
+ /**
446
+ * Populate the path filterer with the client's include/exclude lists and the projects include lists
447
+ * @returns the instance of the path filterer
448
+ */
449
+ async rebuildPathFilterer() {
450
+ var _a;
451
+ //dispose of any previous pathFilterer disposables
452
+ (_a = this.pathFiltererDisposables) === null || _a === void 0 ? void 0 : _a.forEach(dispose => dispose());
453
+ //keep track of all the pathFilterer disposables so we can dispose them later
454
+ this.pathFiltererDisposables = [];
455
+ const workspaceConfigs = await this.getWorkspaceConfigs();
456
+ await Promise.all(workspaceConfigs.map(async (workspaceConfig) => {
457
+ const rootDir = util_1.util.uriToPath(workspaceConfig.workspaceFolder);
458
+ //always exclude everything from these common folders
459
+ this.pathFiltererDisposables.push(this.pathFilterer.registerExcludeList(rootDir, [
460
+ '**/node_modules/**/*',
461
+ '**/.git/**/*',
462
+ 'out/**/*',
463
+ '**/.roku-deploy-staging/**/*'
464
+ ]));
465
+ //get any `files.exclude` patterns from the client from this workspace
466
+ this.pathFiltererDisposables.push(this.pathFilterer.registerExcludeList(rootDir, workspaceConfig.excludePatterns));
467
+ //get any .gitignore patterns from the client from this workspace
468
+ const gitignorePath = path.resolve(rootDir, '.gitignore');
469
+ if (await fsExtra.pathExists(gitignorePath)) {
470
+ const matcher = (0, ignore_1.default)({ ignoreCase: true }).add(fsExtra.readFileSync(gitignorePath).toString());
471
+ this.pathFiltererDisposables.push(this.pathFilterer.registerExcludeMatcher((p) => {
472
+ const relPath = path.relative(rootDir, p);
473
+ if (ignore_1.default.isPathValid(relPath)) {
474
+ return matcher.test(relPath).ignored;
475
+ }
476
+ else {
477
+ //we do not have a valid relative path, so we cannot determine if it is ignored...thus it is NOT ignored
478
+ return false;
479
+ }
480
+ }));
1060
481
  }
482
+ }));
483
+ this.logger.log('pathFilterer successfully reconstructed');
484
+ return this.pathFilterer;
485
+ }
486
+ /**
487
+ * Ask the client for the list of `files.exclude` patterns. Useful when determining if we should process a file
488
+ */
489
+ async getWorkspaceExcludeGlobs(workspaceFolder) {
490
+ var _a;
491
+ const config = await this.getClientConfiguration(workspaceFolder, 'files');
492
+ const result = Object
493
+ .keys((_a = config === null || config === void 0 ? void 0 : config.exclude) !== null && _a !== void 0 ? _a : {})
494
+ .filter(x => { var _a; return (_a = config === null || config === void 0 ? void 0 : config.exclude) === null || _a === void 0 ? void 0 : _a[x]; })
495
+ //vscode files.exclude patterns support ignoring folders without needing to add `**/*`. So for our purposes, we need to
496
+ //append **/* to everything without a file extension or magic at the end
497
+ .map(pattern => [
498
+ //send the pattern as-is (this handles weird cases and exact file matches)
499
+ pattern,
500
+ //treat the pattern as a directory (no harm in doing this because if it's a file, the pattern will just never match anything)
501
+ `${pattern}/**/*`
502
+ ])
503
+ .flat(1);
504
+ return result;
505
+ }
506
+ /**
507
+ * Ask the project manager to sync all projects found within the list of workspaces
508
+ * @param forceReload if true, all projects are discarded and recreated from scratch
509
+ */
510
+ async syncProjects(forceReload = false) {
511
+ const workspaces = await this.getWorkspaceConfigs();
512
+ await this.projectManager.syncProjects(workspaces, forceReload);
513
+ //set our logLevel to the most verbose log level found across all projects and workspaces
514
+ await this.syncLogLevel();
515
+ }
516
+ /**
517
+ * Given a workspaceFolder path, get the specified configuration from the client (if applicable).
518
+ * Be sure to use optional chaining to traverse the result in case that configuration doesn't exist or the client doesn't support `getConfiguration`
519
+ * @param workspaceFolder the folder for the workspace in the client
520
+ */
521
+ async getClientConfiguration(workspaceFolder, section) {
522
+ const scopeUri = util_1.util.pathToUri(workspaceFolder);
523
+ let config = {};
524
+ //if the client supports configuration, look for config group called "brightscript"
525
+ if (this.hasConfigurationCapability) {
526
+ config = await this.connection.workspace.getConfiguration({
527
+ scopeUri: scopeUri,
528
+ section: section
529
+ });
1061
530
  }
531
+ return config;
532
+ }
533
+ /**
534
+ * Send a critical failure notification to the client, which should show a notification of some kind
535
+ */
536
+ sendCriticalFailure(message) {
537
+ this.connection.sendNotification('critical-failure', message).catch(logAndIgnoreError);
538
+ }
539
+ /**
540
+ * Send diagnostics to the client
541
+ */
542
+ async sendDiagnostics(options) {
543
+ const patch = this.diagnosticCollection.getPatch(options.project.projectNumber, options.diagnostics);
544
+ await Promise.all(Object.keys(patch).map(async (srcPath) => {
545
+ const uri = vscode_uri_1.URI.file(srcPath).toString();
546
+ const diagnostics = patch[srcPath].map(d => util_1.util.toDiagnostic(d, uri));
547
+ await this.connection.sendDiagnostics({
548
+ uri: uri,
549
+ diagnostics: diagnostics
550
+ });
551
+ }));
1062
552
  }
1063
553
  dispose() {
1064
- var _a;
554
+ var _a, _b, _c;
1065
555
  (_a = this.loggerSubscription) === null || _a === void 0 ? void 0 : _a.call(this);
1066
- this.validateThrottler.dispose();
556
+ (_c = (_b = this.projectManager) === null || _b === void 0 ? void 0 : _b.dispose) === null || _c === void 0 ? void 0 : _c.call(_b);
1067
557
  }
1068
558
  }
559
+ /**
560
+ * The default threading setting for the language server. Can be overridden by per-workspace settings
561
+ */
562
+ LanguageServer.enableThreadingDefault = true;
1069
563
  __decorate([
1070
564
  AddStackToErrorMessage
1071
565
  ], LanguageServer.prototype, "onInitialize", null);
1072
566
  __decorate([
1073
- TrackBusyStatus
1074
- ], LanguageServer.prototype, "getProjectPaths", null);
1075
- __decorate([
1076
- TrackBusyStatus
1077
- ], LanguageServer.prototype, "syncProjects", null);
1078
- __decorate([
1079
- AddStackToErrorMessage,
1080
- TrackBusyStatus
567
+ AddStackToErrorMessage
1081
568
  ], LanguageServer.prototype, "onInitialized", null);
1082
569
  __decorate([
1083
- TrackBusyStatus
1084
- ], LanguageServer.prototype, "createProject", null);
570
+ AddStackToErrorMessage
571
+ ], LanguageServer.prototype, "onTextDocumentDidChangeContent", null);
1085
572
  __decorate([
1086
- AddStackToErrorMessage,
1087
- TrackBusyStatus
1088
- ], LanguageServer.prototype, "onCompletion", null);
573
+ AddStackToErrorMessage
574
+ ], LanguageServer.prototype, "onDidChangeWatchedFiles", null);
1089
575
  __decorate([
1090
576
  AddStackToErrorMessage
1091
- ], LanguageServer.prototype, "onCompletionResolve", null);
577
+ ], LanguageServer.prototype, "onDocumentClose", null);
1092
578
  __decorate([
1093
- AddStackToErrorMessage,
1094
- TrackBusyStatus
1095
- ], LanguageServer.prototype, "onCodeAction", null);
579
+ AddStackToErrorMessage
580
+ ], LanguageServer.prototype, "onCompletion", null);
1096
581
  __decorate([
1097
582
  AddStackToErrorMessage
1098
583
  ], LanguageServer.prototype, "onDidChangeConfiguration", null);
1099
- __decorate([
1100
- AddStackToErrorMessage,
1101
- TrackBusyStatus
1102
- ], LanguageServer.prototype, "onDidChangeWatchedFiles", null);
1103
584
  __decorate([
1104
585
  AddStackToErrorMessage
1105
586
  ], LanguageServer.prototype, "onHover", null);
1106
587
  __decorate([
1107
588
  AddStackToErrorMessage
1108
- ], LanguageServer.prototype, "onDocumentClose", null);
1109
- __decorate([
1110
- AddStackToErrorMessage,
1111
- TrackBusyStatus
1112
- ], LanguageServer.prototype, "validateTextDocument", null);
1113
- __decorate([
1114
- TrackBusyStatus
1115
- ], LanguageServer.prototype, "validateAll", null);
1116
- __decorate([
1117
- AddStackToErrorMessage,
1118
- TrackBusyStatus
1119
589
  ], LanguageServer.prototype, "onWorkspaceSymbol", null);
1120
590
  __decorate([
1121
- AddStackToErrorMessage,
1122
- TrackBusyStatus
591
+ AddStackToErrorMessage
1123
592
  ], LanguageServer.prototype, "onDocumentSymbol", null);
1124
593
  __decorate([
1125
- AddStackToErrorMessage,
1126
- TrackBusyStatus
594
+ AddStackToErrorMessage
1127
595
  ], LanguageServer.prototype, "onDefinition", null);
1128
596
  __decorate([
1129
- AddStackToErrorMessage,
1130
- TrackBusyStatus
597
+ AddStackToErrorMessage
1131
598
  ], LanguageServer.prototype, "onSignatureHelp", null);
1132
599
  __decorate([
1133
- AddStackToErrorMessage,
1134
- TrackBusyStatus
600
+ AddStackToErrorMessage
1135
601
  ], LanguageServer.prototype, "onReferences", null);
1136
602
  __decorate([
1137
- AddStackToErrorMessage,
1138
- TrackBusyStatus
603
+ AddStackToErrorMessage
1139
604
  ], LanguageServer.prototype, "onFullSemanticTokens", null);
1140
605
  __decorate([
1141
- AddStackToErrorMessage,
1142
- TrackBusyStatus
606
+ AddStackToErrorMessage
607
+ ], LanguageServer.prototype, "onCodeAction", null);
608
+ __decorate([
609
+ AddStackToErrorMessage
1143
610
  ], LanguageServer.prototype, "onExecuteCommand", null);
1144
611
  exports.LanguageServer = LanguageServer;
1145
612
  var CustomCommands;
@@ -1181,16 +648,10 @@ function AddStackToErrorMessage(target, propertyKey, descriptor) {
1181
648
  }
1182
649
  };
1183
650
  }
1184
- /**
1185
- * An annotation used to wrap the method in a busyStatus tracking call
1186
- */
1187
- function TrackBusyStatus(target, propertyKey, descriptor) {
1188
- let originalMethod = descriptor.value;
1189
- //wrapping the original method
1190
- descriptor.value = function value(...args) {
1191
- return this.busyStatusTracker.run(() => {
1192
- return originalMethod.apply(this, args);
1193
- }, originalMethod.name);
1194
- };
651
+ function logAndIgnoreError(error) {
652
+ if (error === null || error === void 0 ? void 0 : error.stack) {
653
+ error.message = error.stack;
654
+ }
655
+ console.error(error);
1195
656
  }
1196
657
  //# sourceMappingURL=LanguageServer.js.map