roku-debug 0.8.4 → 0.9.0
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 +40 -0
- package/dist/ChanperfTracker.js +8 -10
- package/dist/ChanperfTracker.js.map +1 -1
- package/dist/CompileErrorProcessor.d.ts +2 -1
- package/dist/CompileErrorProcessor.js +7 -5
- package/dist/CompileErrorProcessor.js.map +1 -1
- package/dist/ComponentLibraryServer.js +1 -0
- package/dist/ComponentLibraryServer.js.map +1 -1
- package/dist/FileUtils.d.ts +2 -2
- package/dist/FileUtils.js +4 -4
- package/dist/FileUtils.js.map +1 -1
- package/dist/LaunchConfiguration.d.ts +5 -0
- package/dist/PrintedObjectParser.js +1 -0
- package/dist/PrintedObjectParser.js.map +1 -1
- package/dist/RendezvousTracker.js +6 -8
- package/dist/RendezvousTracker.js.map +1 -1
- package/dist/SceneGraphDebugCommandController.d.ts +1 -0
- package/dist/SceneGraphDebugCommandController.js +14 -7
- package/dist/SceneGraphDebugCommandController.js.map +1 -1
- package/dist/adapters/DebugProtocolAdapter.d.ts +7 -15
- package/dist/adapters/DebugProtocolAdapter.js +32 -30
- package/dist/adapters/DebugProtocolAdapter.js.map +1 -1
- package/dist/adapters/TelnetAdapter.d.ts +11 -58
- package/dist/adapters/TelnetAdapter.js +262 -382
- package/dist/adapters/TelnetAdapter.js.map +1 -1
- package/dist/adapters/TelnetRequestPipeline.d.ts +51 -0
- package/dist/adapters/TelnetRequestPipeline.js +229 -0
- package/dist/adapters/TelnetRequestPipeline.js.map +1 -0
- package/dist/debugProtocol/Constants.js +1 -0
- package/dist/debugProtocol/Constants.js.map +1 -1
- package/dist/debugProtocol/Debugger.d.ts +9 -8
- package/dist/debugProtocol/Debugger.js +33 -28
- package/dist/debugProtocol/Debugger.js.map +1 -1
- package/dist/debugProtocol/responses/ConnectIOPortResponse.js +1 -0
- package/dist/debugProtocol/responses/ConnectIOPortResponse.js.map +1 -1
- package/dist/debugProtocol/responses/HandshakeResponse.js +1 -0
- package/dist/debugProtocol/responses/HandshakeResponse.js.map +1 -1
- package/dist/debugProtocol/responses/Response.js +1 -0
- package/dist/debugProtocol/responses/Response.js.map +1 -1
- package/dist/debugProtocol/responses/StackTraceResponse.js +1 -0
- package/dist/debugProtocol/responses/StackTraceResponse.js.map +1 -1
- package/dist/debugProtocol/responses/ThreadsResponse.js +1 -0
- package/dist/debugProtocol/responses/ThreadsResponse.js.map +1 -1
- package/dist/debugProtocol/responses/UndefinedResponse.js +1 -0
- package/dist/debugProtocol/responses/UndefinedResponse.js.map +1 -1
- package/dist/debugProtocol/responses/UpdateThreadsResponse.js +1 -0
- package/dist/debugProtocol/responses/UpdateThreadsResponse.js.map +1 -1
- package/dist/debugProtocol/responses/VariableResponse.d.ts +1 -1
- package/dist/debugProtocol/responses/VariableResponse.js +1 -0
- package/dist/debugProtocol/responses/VariableResponse.js.map +1 -1
- package/dist/debugProtocol/responses/index.js +18 -11
- package/dist/debugProtocol/responses/index.js.map +1 -1
- package/dist/debugSession/BrightScriptDebugSession.d.ts +12 -9
- package/dist/debugSession/BrightScriptDebugSession.js +114 -92
- package/dist/debugSession/BrightScriptDebugSession.js.map +1 -1
- package/dist/debugSession/Events.js +1 -0
- package/dist/debugSession/Events.js.map +1 -1
- package/dist/index.js +26 -18
- package/dist/index.js.map +1 -1
- package/dist/interfaces.d.ts +11 -0
- package/dist/interfaces.js +16 -0
- package/dist/interfaces.js.map +1 -0
- package/dist/logging.d.ts +11 -0
- package/dist/logging.js +14 -0
- package/dist/logging.js.map +1 -0
- package/dist/managers/BreakpointManager.js +4 -4
- package/dist/managers/BreakpointManager.js.map +1 -1
- package/dist/managers/FileManager.d.ts +2 -1
- package/dist/managers/FileManager.js +9 -6
- package/dist/managers/FileManager.js.map +1 -1
- package/dist/managers/LocationManager.js +8 -7
- package/dist/managers/LocationManager.js.map +1 -1
- package/dist/managers/ProjectManager.d.ts +2 -0
- package/dist/managers/ProjectManager.js +14 -11
- package/dist/managers/ProjectManager.js.map +1 -1
- package/dist/managers/SourceMapManager.d.ts +1 -0
- package/dist/managers/SourceMapManager.js +9 -8
- package/dist/managers/SourceMapManager.js.map +1 -1
- package/dist/util.d.ts +50 -14
- package/dist/util.js +136 -75
- package/dist/util.js.map +1 -1
- package/package.json +52 -40
- package/roku-debug-0.9.0.tgz +0 -0
- package/roku-debug-0.8.4.tgz +0 -0
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
|
|
4
|
-
const EventEmitter = require("events");
|
|
3
|
+
exports.PrimativeType = exports.KeyType = exports.EventName = exports.TelnetAdapter = void 0;
|
|
5
4
|
const natural_orderby_1 = require("natural-orderby");
|
|
6
|
-
const
|
|
5
|
+
const EventEmitter = require("eventemitter3");
|
|
6
|
+
const net_1 = require("net");
|
|
7
7
|
const rokuDeploy = require("roku-deploy");
|
|
8
|
-
const BrightScriptDebugSession_1 = require("../debugSession/BrightScriptDebugSession");
|
|
9
8
|
const PrintedObjectParser_1 = require("../PrintedObjectParser");
|
|
10
9
|
const CompileErrorProcessor_1 = require("../CompileErrorProcessor");
|
|
11
10
|
const RendezvousTracker_1 = require("../RendezvousTracker");
|
|
12
11
|
const ChanperfTracker_1 = require("../ChanperfTracker");
|
|
13
12
|
const util_1 = require("../util");
|
|
13
|
+
const logging_1 = require("../logging");
|
|
14
|
+
const interfaces_1 = require("../interfaces");
|
|
15
|
+
const TelnetRequestPipeline_1 = require("./TelnetRequestPipeline");
|
|
14
16
|
/**
|
|
15
17
|
* A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it.
|
|
16
18
|
*/
|
|
@@ -18,6 +20,7 @@ class TelnetAdapter {
|
|
|
18
20
|
constructor(host, enableDebuggerAutoRecovery = false) {
|
|
19
21
|
this.host = host;
|
|
20
22
|
this.enableDebuggerAutoRecovery = enableDebuggerAutoRecovery;
|
|
23
|
+
this.logger = logging_1.logger.createLogger(`[${TelnetAdapter.name}]`);
|
|
21
24
|
this.isNextBreakpointSkipped = false;
|
|
22
25
|
this.cache = {};
|
|
23
26
|
this.supportsMultipleRuns = true;
|
|
@@ -72,6 +75,7 @@ class TelnetAdapter {
|
|
|
72
75
|
}, 0);
|
|
73
76
|
}
|
|
74
77
|
async activate() {
|
|
78
|
+
this.logger.log('Activate TelnetAdapter');
|
|
75
79
|
this.isActivated = true;
|
|
76
80
|
await this.handleStartupIfReady();
|
|
77
81
|
}
|
|
@@ -79,14 +83,17 @@ class TelnetAdapter {
|
|
|
79
83
|
await this.compileErrorProcessor.sendErrors();
|
|
80
84
|
}
|
|
81
85
|
async handleStartupIfReady() {
|
|
86
|
+
var _a;
|
|
82
87
|
if (this.isActivated && this.isAppRunning) {
|
|
88
|
+
this.logger.log('Handling startup');
|
|
83
89
|
this.emit('start');
|
|
84
90
|
//if we are already sitting at a debugger prompt, we need to emit the first suspend event.
|
|
85
91
|
//If not, then there are probably still messages being received, so let the normal handler
|
|
86
92
|
//emit the suspend event when it's ready
|
|
87
93
|
if (this.isAtDebuggerPrompt === true) {
|
|
94
|
+
this.logger.log(`At debug prompt, so trigger the 'suspend' event`);
|
|
88
95
|
let threads = await this.getThreads();
|
|
89
|
-
this.emit('suspend', threads[0].threadId);
|
|
96
|
+
this.emit('suspend', (_a = threads[0]) === null || _a === void 0 ? void 0 : _a.threadId);
|
|
90
97
|
}
|
|
91
98
|
}
|
|
92
99
|
}
|
|
@@ -97,73 +104,79 @@ class TelnetAdapter {
|
|
|
97
104
|
* @param maxWaitMilliseconds
|
|
98
105
|
*/
|
|
99
106
|
settle(client, name, maxWaitMilliseconds = 400) {
|
|
107
|
+
const startTime = new Date();
|
|
108
|
+
this.logger.log('Waiting for telnet client to settle');
|
|
100
109
|
return new Promise((resolve) => {
|
|
101
110
|
let callCount = -1;
|
|
102
|
-
|
|
111
|
+
const handler = () => {
|
|
103
112
|
callCount++;
|
|
104
113
|
let myCallCount = callCount;
|
|
105
114
|
setTimeout(() => {
|
|
106
115
|
//if no other calls have been made since the timeout started, then the listener has settled
|
|
107
116
|
if (myCallCount === callCount) {
|
|
108
117
|
client.removeListener(name, handler);
|
|
118
|
+
this.logger.log(`Telnet client has settled after ${new Date().getTime() - startTime.getTime()} milliseconds`);
|
|
109
119
|
resolve(callCount);
|
|
110
120
|
}
|
|
111
121
|
}, maxWaitMilliseconds);
|
|
112
|
-
}
|
|
122
|
+
};
|
|
113
123
|
client.addListener(name, handler);
|
|
114
124
|
//call the handler immediately so we have a timeout
|
|
115
125
|
handler();
|
|
116
126
|
});
|
|
117
127
|
}
|
|
118
128
|
processBreakpoints(text) {
|
|
119
|
-
let newLines =
|
|
129
|
+
let newLines = text.split(/\r?\n/g);
|
|
120
130
|
for (const line of newLines) {
|
|
121
|
-
|
|
131
|
+
//Running processing line
|
|
122
132
|
if (this.debugStartRegex.exec(line)) {
|
|
123
|
-
|
|
133
|
+
//start MicroDebugger block
|
|
124
134
|
this.isInMicroDebugger = true;
|
|
125
135
|
this.isNextBreakpointSkipped = false;
|
|
126
136
|
}
|
|
127
137
|
else if (this.isInMicroDebugger && this.debugEndRegex.exec(line)) {
|
|
128
|
-
|
|
138
|
+
//ended MicroDebugger block
|
|
129
139
|
this.isInMicroDebugger = false;
|
|
130
140
|
}
|
|
131
141
|
else if (this.isInMicroDebugger) {
|
|
132
142
|
if (this.enableDebuggerAutoRecovery && line.startsWith('Break in ')) {
|
|
133
|
-
|
|
143
|
+
//this block is a break: skipping it
|
|
134
144
|
this.isNextBreakpointSkipped = true;
|
|
135
145
|
}
|
|
136
146
|
}
|
|
137
147
|
}
|
|
138
|
-
return text;
|
|
139
148
|
}
|
|
140
149
|
/**
|
|
141
150
|
* Connect to the telnet session. This should be called before the channel is launched.
|
|
142
151
|
*/
|
|
143
152
|
async connect() {
|
|
144
|
-
|
|
153
|
+
this.logger.log('Establishing telnet connection');
|
|
154
|
+
let deferred = (0, util_1.defer)();
|
|
145
155
|
this.isInMicroDebugger = false;
|
|
146
156
|
this.isNextBreakpointSkipped = false;
|
|
147
157
|
try {
|
|
158
|
+
this.logger.log('Pressing home button');
|
|
148
159
|
//force roku to return to home screen. This gives the roku adapter some security in knowing new messages won't be appearing during initialization
|
|
149
160
|
await rokuDeploy.pressHomeButton(this.host);
|
|
150
|
-
let client = new
|
|
151
|
-
client.connect(8085, this.host, () => {
|
|
152
|
-
console.log(`+++++++++++ CONNECTED TO DEVICE ${this.host} +++++++++++`);
|
|
153
|
-
this.connected = true;
|
|
154
|
-
this.emit('connected', this.connected);
|
|
155
|
-
});
|
|
161
|
+
let client = new net_1.Socket();
|
|
156
162
|
//listen for the close event
|
|
157
163
|
client.addListener('close', (err, data) => {
|
|
158
164
|
this.emit('close');
|
|
159
165
|
});
|
|
160
166
|
//if the connection fails, reject the connect promise
|
|
161
167
|
client.addListener('error', (err) => {
|
|
162
|
-
deferred.reject(new Error(`Error with connection to: ${this.host} \n\n ${err.message}`));
|
|
168
|
+
deferred.reject(new Error(`Error with connection to: ${this.host} \n\n ${err.message} `));
|
|
163
169
|
});
|
|
164
|
-
|
|
170
|
+
const settlePromise = this.settle(client, 'data');
|
|
171
|
+
client.connect(8085, this.host, () => {
|
|
172
|
+
this.logger.log(`Telnet connection established to ${this.host}`);
|
|
173
|
+
this.connected = true;
|
|
174
|
+
this.emit('connected', this.connected);
|
|
175
|
+
});
|
|
176
|
+
await settlePromise;
|
|
165
177
|
//hook up the pipeline to the socket
|
|
166
|
-
this.requestPipeline = new
|
|
178
|
+
this.requestPipeline = new TelnetRequestPipeline_1.TelnetRequestPipeline(client);
|
|
179
|
+
this.requestPipeline.connect();
|
|
167
180
|
//forward all raw console output
|
|
168
181
|
this.requestPipeline.on('console-output', (output) => {
|
|
169
182
|
this.processBreakpoints(output);
|
|
@@ -189,13 +202,14 @@ class TelnetAdapter {
|
|
|
189
202
|
}
|
|
190
203
|
// short circuit after the output has been sent as console output
|
|
191
204
|
if (hasRuntimeError) {
|
|
192
|
-
|
|
205
|
+
this.logger.log('Detected runtime error in output', { responseText });
|
|
193
206
|
this.isAtDebuggerPrompt = true;
|
|
194
207
|
return;
|
|
195
208
|
}
|
|
196
|
-
this.processUnhandledLines(responseText);
|
|
209
|
+
this.compileErrorProcessor.processUnhandledLines(responseText);
|
|
197
210
|
let match;
|
|
198
211
|
if (this.isAtCannotContinue(responseText)) {
|
|
212
|
+
this.logger.log('is at cannot continue');
|
|
199
213
|
this.isAtDebuggerPrompt = true;
|
|
200
214
|
return;
|
|
201
215
|
}
|
|
@@ -204,6 +218,7 @@ class TelnetAdapter {
|
|
|
204
218
|
// eslint-disable-next-line no-cond-assign
|
|
205
219
|
if (match = /\[scrpt.ctx.run.enter\]/i.exec(responseText.trim())) {
|
|
206
220
|
this.isAppRunning = true;
|
|
221
|
+
this.logger.log('Running beacon detected', { responseText });
|
|
207
222
|
void this.handleStartupIfReady();
|
|
208
223
|
}
|
|
209
224
|
//watch for the end of the program
|
|
@@ -212,26 +227,29 @@ class TelnetAdapter {
|
|
|
212
227
|
this.beginAppExit();
|
|
213
228
|
}
|
|
214
229
|
//watch for debugger prompt output
|
|
215
|
-
|
|
216
|
-
|
|
230
|
+
if (util_1.util.endsWithDebuggerPrompt(responseText)) {
|
|
231
|
+
this.logger.log('Debugger prompt detected in', { responseText });
|
|
217
232
|
//if we are activated AND this is the first time seeing the debugger prompt since a continue/step action
|
|
218
233
|
if (this.isNextBreakpointSkipped) {
|
|
219
|
-
|
|
234
|
+
this.logger.log('This debugger is flagged to be skipped');
|
|
220
235
|
this.isInMicroDebugger = false;
|
|
221
236
|
this.isNextBreakpointSkipped = false;
|
|
222
|
-
void this.requestPipeline.executeCommand('c',
|
|
237
|
+
void this.requestPipeline.executeCommand('c', { waitForPrompt: false, insertAtFront: true });
|
|
223
238
|
}
|
|
224
239
|
else {
|
|
225
240
|
if (this.isActivated && this.isAtDebuggerPrompt === false) {
|
|
226
241
|
this.isAtDebuggerPrompt = true;
|
|
242
|
+
this.logger.log('Sending the "suspend" event to the client');
|
|
227
243
|
this.emit('suspend');
|
|
228
244
|
}
|
|
229
245
|
else {
|
|
246
|
+
this.logger.log('Skipping "suspend" event because we are already suspended');
|
|
230
247
|
this.isAtDebuggerPrompt = true;
|
|
231
248
|
}
|
|
232
249
|
}
|
|
233
250
|
}
|
|
234
251
|
else {
|
|
252
|
+
this.logger.debug('responseText does not end with debugger prompt. isAtDebuggerPrompt = false', { responseText });
|
|
235
253
|
this.isAtDebuggerPrompt = false;
|
|
236
254
|
}
|
|
237
255
|
}
|
|
@@ -245,7 +263,9 @@ class TelnetAdapter {
|
|
|
245
263
|
return deferred.promise;
|
|
246
264
|
}
|
|
247
265
|
beginAppExit() {
|
|
266
|
+
this.logger.log('Beginning app exit');
|
|
248
267
|
this.compileErrorProcessor.compileErrorTimer = setTimeout(() => {
|
|
268
|
+
this.logger.info('emitting app-exit');
|
|
249
269
|
this.isAppRunning = false;
|
|
250
270
|
this.emit('app-exit');
|
|
251
271
|
}, 200);
|
|
@@ -286,43 +306,46 @@ class TelnetAdapter {
|
|
|
286
306
|
return false;
|
|
287
307
|
}
|
|
288
308
|
}
|
|
289
|
-
processUnhandledLines(responseText) {
|
|
290
|
-
this.compileErrorProcessor.processUnhandledLines(responseText);
|
|
291
|
-
}
|
|
292
309
|
/**
|
|
293
310
|
* Send command to step over
|
|
294
311
|
*/
|
|
295
312
|
stepOver() {
|
|
313
|
+
this.logger.log('stepOver');
|
|
296
314
|
this.clearCache();
|
|
297
|
-
return this.requestPipeline.executeCommand('over', false);
|
|
315
|
+
return this.requestPipeline.executeCommand('over', { waitForPrompt: false, insertAtFront: true });
|
|
298
316
|
}
|
|
299
317
|
stepInto() {
|
|
318
|
+
this.logger.log('stepInto');
|
|
300
319
|
this.clearCache();
|
|
301
|
-
return this.requestPipeline.executeCommand('step', false);
|
|
320
|
+
return this.requestPipeline.executeCommand('step', { waitForPrompt: false, insertAtFront: true });
|
|
302
321
|
}
|
|
303
322
|
stepOut() {
|
|
323
|
+
this.logger.log('stepOut');
|
|
304
324
|
this.clearCache();
|
|
305
|
-
return this.requestPipeline.executeCommand('out', false);
|
|
325
|
+
return this.requestPipeline.executeCommand('out', { waitForPrompt: false, insertAtFront: true });
|
|
306
326
|
}
|
|
307
327
|
/**
|
|
308
328
|
* Tell the brightscript program to continue (i.e. resume program)
|
|
309
329
|
*/
|
|
310
330
|
continue() {
|
|
331
|
+
this.logger.log('continue');
|
|
311
332
|
this.clearCache();
|
|
312
|
-
return this.requestPipeline.executeCommand('c', false);
|
|
333
|
+
return this.requestPipeline.executeCommand('c', { waitForPrompt: false, insertAtFront: true });
|
|
313
334
|
}
|
|
314
335
|
/**
|
|
315
336
|
* Tell the brightscript program to pause (fall into debug mode)
|
|
316
337
|
*/
|
|
317
338
|
pause() {
|
|
339
|
+
this.logger.log('pause');
|
|
318
340
|
this.clearCache();
|
|
319
|
-
//send the kill signal, which breaks into debugger mode
|
|
320
|
-
|
|
341
|
+
//send the kill signal, which breaks into debugger mode. This gets written immediately, regardless of debugger prompt status.
|
|
342
|
+
this.requestPipeline.write('\x03;');
|
|
321
343
|
}
|
|
322
344
|
/**
|
|
323
345
|
* Clears the state, which means that everything will be retrieved fresh next time it is requested
|
|
324
346
|
*/
|
|
325
347
|
clearCache() {
|
|
348
|
+
this.logger.info('Clearing TelnetAdapter cache');
|
|
326
349
|
this.cache = {};
|
|
327
350
|
this.isAtDebuggerPrompt = false;
|
|
328
351
|
}
|
|
@@ -331,24 +354,26 @@ class TelnetAdapter {
|
|
|
331
354
|
* @param command the command to execute. If the command does not start with `print` the command will be prefixed with `print ` because
|
|
332
355
|
*/
|
|
333
356
|
async evaluate(command) {
|
|
357
|
+
this.logger.log('evaluate ', { command });
|
|
334
358
|
if (!this.isAtDebuggerPrompt) {
|
|
335
359
|
throw new Error('Cannot run evaluate: debugger is not paused');
|
|
336
360
|
}
|
|
337
361
|
//clear the cache (we don't know what command the user entered)
|
|
338
362
|
this.clearCache();
|
|
339
363
|
//don't wait for the output...we don't know what command the user entered
|
|
340
|
-
let responseText = await this.requestPipeline.executeCommand(command, true);
|
|
364
|
+
let responseText = await this.requestPipeline.executeCommand(command, { waitForPrompt: true });
|
|
341
365
|
//we know that if we got a response, we are back at a debugger prompt
|
|
342
366
|
this.isAtDebuggerPrompt = true;
|
|
343
367
|
return responseText;
|
|
344
368
|
}
|
|
345
369
|
async getStackTrace() {
|
|
370
|
+
this.logger.log(TelnetAdapter.prototype.getStackTrace.name);
|
|
346
371
|
if (!this.isAtDebuggerPrompt) {
|
|
347
372
|
throw new Error('Cannot get stack trace: debugger is not paused');
|
|
348
373
|
}
|
|
349
374
|
return this.resolve('stackTrace', async () => {
|
|
350
375
|
//perform a request to load the stack trace
|
|
351
|
-
let responseText = await this.requestPipeline.executeCommand('bt', true);
|
|
376
|
+
let responseText = (await this.requestPipeline.executeCommand('bt', { waitForPrompt: true })).trim();
|
|
352
377
|
let regexp = /#(\d+)\s+(?:function|sub)\s+([\$\w\d]+).*\s+file\/line:\s+(.*)\((\d+)\)/ig;
|
|
353
378
|
let matches;
|
|
354
379
|
let frames = [];
|
|
@@ -375,16 +400,6 @@ class TelnetAdapter {
|
|
|
375
400
|
return frames;
|
|
376
401
|
});
|
|
377
402
|
}
|
|
378
|
-
/**
|
|
379
|
-
* Runs a regex to get the content between telnet commands
|
|
380
|
-
* @param value
|
|
381
|
-
*/
|
|
382
|
-
getExpressionDetails(value) {
|
|
383
|
-
const match = /(.*?)\r?\nBrightscript Debugger>\s*/is.exec(value);
|
|
384
|
-
if (match) {
|
|
385
|
-
return match[1];
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
403
|
/**
|
|
389
404
|
* Runs a regex to check if the target is an object and get the type if it is
|
|
390
405
|
* @param value
|
|
@@ -410,14 +425,15 @@ class TelnetAdapter {
|
|
|
410
425
|
* @param scope
|
|
411
426
|
*/
|
|
412
427
|
async getScopeVariables(scope) {
|
|
428
|
+
this.logger.log('getScopeVariables', { scope });
|
|
413
429
|
if (!this.isAtDebuggerPrompt) {
|
|
414
430
|
throw new Error('Cannot resolve variable: debugger is not paused');
|
|
415
431
|
}
|
|
416
432
|
return this.resolve(`Scope Variables`, async () => {
|
|
417
433
|
let data;
|
|
418
434
|
let vars = [];
|
|
419
|
-
data = await this.requestPipeline.executeCommand(`var`, true);
|
|
420
|
-
let splitData = data.split('\n');
|
|
435
|
+
data = await this.requestPipeline.executeCommand(`var`, { waitForPrompt: true });
|
|
436
|
+
let splitData = data.trim().split('\n');
|
|
421
437
|
for (const line of splitData) {
|
|
422
438
|
let match;
|
|
423
439
|
if (!line.includes('Brightscript Debugger') && (match = this.getFirstWord(line))) {
|
|
@@ -436,97 +452,141 @@ class TelnetAdapter {
|
|
|
436
452
|
* @param expression
|
|
437
453
|
*/
|
|
438
454
|
async getVariable(expression) {
|
|
455
|
+
const logger = this.logger.createLogger('[getVariable]');
|
|
456
|
+
logger.info('begin', { expression });
|
|
439
457
|
if (!this.isAtDebuggerPrompt) {
|
|
440
458
|
throw new Error('Cannot resolve variable: debugger is not paused');
|
|
441
459
|
}
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
}
|
|
529
|
-
|
|
460
|
+
let expressionType = await this.getVariableType(expression);
|
|
461
|
+
let lowerExpressionType = expressionType ? expressionType.toLowerCase() : null;
|
|
462
|
+
let data;
|
|
463
|
+
//if the expression type is a string, we need to wrap the expression in quotes BEFORE we run the print so we can accurately capture the full string value
|
|
464
|
+
if (lowerExpressionType === 'string' || lowerExpressionType === 'rostring') {
|
|
465
|
+
data = await this.requestPipeline.executeCommand(`print "--string-wrap--" + ${expression} + "--string-wrap--"`, { waitForPrompt: true });
|
|
466
|
+
//write a for loop to print every value from the array. This gets around the `...` after the 100th item issue in the roku print call
|
|
467
|
+
}
|
|
468
|
+
else if (['roarray', 'rolist', 'roxmllist', 'robytearray'].includes(lowerExpressionType)) {
|
|
469
|
+
const command = [
|
|
470
|
+
`for each vscodeLoopItem in ${expression} : print ` +
|
|
471
|
+
` "vscode_type_start:" + type(vscodeLoopItem) + ":vscode_type_stop "`,
|
|
472
|
+
` "vscode_is_string:"; (invalid <> GetInterface(vscodeLoopItem, "ifString"))`,
|
|
473
|
+
` vscodeLoopItem :` +
|
|
474
|
+
` end for`
|
|
475
|
+
].join(';');
|
|
476
|
+
data = await this.requestPipeline.executeCommand(command, { waitForPrompt: true });
|
|
477
|
+
}
|
|
478
|
+
else if (['roassociativearray', 'rosgnode'].includes(lowerExpressionType)) {
|
|
479
|
+
const command = [
|
|
480
|
+
`for each vscodeLoopKey in ${expression}.keys(): print` +
|
|
481
|
+
` "vscode_key_start:" + vscodeLoopKey + ":vscode_key_stop "`,
|
|
482
|
+
` "vscode_type_start:" + type(${expression}[vscodeLoopKey]) + ":vscode_type_stop "`,
|
|
483
|
+
` "vscode_is_string:"; (invalid <> GetInterface(${expression}[vscodeLoopKey], "ifString"))`,
|
|
484
|
+
` ${expression}[vscodeLoopKey] :` +
|
|
485
|
+
' end for'
|
|
486
|
+
].join(';');
|
|
487
|
+
data = await this.requestPipeline.executeCommand(command, { waitForPrompt: true });
|
|
488
|
+
}
|
|
489
|
+
else {
|
|
490
|
+
data = await this.requestPipeline.executeCommand(`print ${expression}`, { waitForPrompt: true });
|
|
491
|
+
}
|
|
492
|
+
logger.info('expression details', { data });
|
|
493
|
+
//remove excess whitespace
|
|
494
|
+
data = data.trim();
|
|
495
|
+
if (lowerExpressionType === 'string' || lowerExpressionType === 'rostring') {
|
|
496
|
+
data = data.trim().replace(/--string-wrap--/g, '');
|
|
497
|
+
//add an escape character in front of any existing quotes
|
|
498
|
+
data = data.replace(/"/g, '\\"');
|
|
499
|
+
//wrap the string value with literal quote marks
|
|
500
|
+
data = '"' + data + '"';
|
|
501
|
+
}
|
|
502
|
+
let highLevelType = this.getHighLevelType(expressionType);
|
|
503
|
+
let children;
|
|
504
|
+
if (highLevelType === interfaces_1.HighLevelType.array || ['roassociativearray', 'rosgnode', 'roxmllist', 'robytearray'].includes(lowerExpressionType)) {
|
|
505
|
+
//the print statment will always have 1 trailing newline, so remove that.
|
|
506
|
+
data = util_1.util.removeTrailingNewline(data);
|
|
507
|
+
//the array/associative array print is a loop of every value, so handle that
|
|
508
|
+
children = this.getForLoopPrintedChildren(expression, data);
|
|
509
|
+
children.push({
|
|
510
|
+
name: '[[count]]',
|
|
511
|
+
value: children.length.toString(),
|
|
512
|
+
type: 'integer',
|
|
513
|
+
highLevelType: interfaces_1.HighLevelType.primative,
|
|
514
|
+
evaluateName: children.length.toString(),
|
|
515
|
+
presentationHint: 'virtual',
|
|
516
|
+
keyType: KeyType.legacy,
|
|
517
|
+
children: undefined
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
else if (highLevelType === interfaces_1.HighLevelType.object) {
|
|
521
|
+
children = this.getObjectChildren(expression, data.trim());
|
|
522
|
+
}
|
|
523
|
+
else if (highLevelType === interfaces_1.HighLevelType.unknown) {
|
|
524
|
+
logger.warn('there was an issue evaluating this variable', { expression });
|
|
525
|
+
data = '<UNKNOWN>';
|
|
526
|
+
}
|
|
527
|
+
if (['rostring', 'roint', 'rointeger', 'rolonginteger', 'rofloat', 'rodouble', 'roboolean', 'rointrinsicdouble'].includes(lowerExpressionType)) {
|
|
528
|
+
return {
|
|
529
|
+
name: expression,
|
|
530
|
+
value: util_1.util.removeTrailingNewline(data),
|
|
531
|
+
type: expressionType,
|
|
532
|
+
highLevelType: interfaces_1.HighLevelType.primative,
|
|
533
|
+
evaluateName: expression,
|
|
534
|
+
children: []
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
//add a computed `[[children]]` property to allow expansion of node children
|
|
538
|
+
if (lowerExpressionType === 'rosgnode') {
|
|
539
|
+
let nodeChildren = {
|
|
540
|
+
name: '[[children]]',
|
|
541
|
+
type: 'roArray',
|
|
542
|
+
highLevelType: 'array',
|
|
543
|
+
presentationHint: 'virtual',
|
|
544
|
+
evaluateName: `${expression}.getChildren(-1, 0)`,
|
|
545
|
+
children: []
|
|
546
|
+
};
|
|
547
|
+
children.push(nodeChildren);
|
|
548
|
+
}
|
|
549
|
+
//xml elements won't display on their own, so we need to create some sub elements
|
|
550
|
+
if (lowerExpressionType === 'roxmlelement') {
|
|
551
|
+
children.push({
|
|
552
|
+
//look up the name of the xml element
|
|
553
|
+
...await this.getVariable(`${expression}.GetName()`),
|
|
554
|
+
name: '[[name]]',
|
|
555
|
+
presentationHint: 'virtual'
|
|
556
|
+
});
|
|
557
|
+
children.push({
|
|
558
|
+
name: '[[attributes]]',
|
|
559
|
+
type: 'roAssociativeArray',
|
|
560
|
+
highLevelType: interfaces_1.HighLevelType.array,
|
|
561
|
+
evaluateName: `${expression}.GetAttributes()`,
|
|
562
|
+
presentationHint: 'virtual',
|
|
563
|
+
children: []
|
|
564
|
+
});
|
|
565
|
+
//add a computed `[[children]]` property to allow expansion of child elements
|
|
566
|
+
children.push({
|
|
567
|
+
name: '[[children]]',
|
|
568
|
+
type: 'roArray',
|
|
569
|
+
highLevelType: interfaces_1.HighLevelType.array,
|
|
570
|
+
evaluateName: `${expression}.GetChildNodes()`,
|
|
571
|
+
presentationHint: 'virtual',
|
|
572
|
+
children: []
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
//if this item is an array or a list, add the item count to the end of the type
|
|
576
|
+
if (highLevelType === interfaces_1.HighLevelType.array) {
|
|
577
|
+
//TODO re-enable once we find how to refresh watch/variables panel, since lazy loaded arrays can't show a length
|
|
578
|
+
//expressionType += `(${children.length})`;
|
|
579
|
+
}
|
|
580
|
+
let container = {
|
|
581
|
+
name: expression,
|
|
582
|
+
evaluateName: expression,
|
|
583
|
+
type: expressionType,
|
|
584
|
+
value: data.trim(),
|
|
585
|
+
highLevelType: highLevelType,
|
|
586
|
+
children: children
|
|
587
|
+
};
|
|
588
|
+
logger.info('end', { container });
|
|
589
|
+
return container;
|
|
530
590
|
}
|
|
531
591
|
/**
|
|
532
592
|
* In order to get around the `...` issue in printed arrays, `getVariable` now prints every value from an array or associative array in a for loop.
|
|
@@ -534,7 +594,7 @@ class TelnetAdapter {
|
|
|
534
594
|
*/
|
|
535
595
|
getForLoopPrintedChildren(expression, data) {
|
|
536
596
|
let children = [];
|
|
537
|
-
let lines =
|
|
597
|
+
let lines = data.split(/\r?\n/g);
|
|
538
598
|
//if there are no lines, this is an empty object/array
|
|
539
599
|
if (lines.length === 1 && lines[0].trim() === '') {
|
|
540
600
|
return children;
|
|
@@ -558,6 +618,17 @@ class TelnetAdapter {
|
|
|
558
618
|
child.name = children.length.toString();
|
|
559
619
|
child.evaluateName = `${expression}[${children.length}]`;
|
|
560
620
|
}
|
|
621
|
+
//get the object type
|
|
622
|
+
let typeStartWrapper = 'vscode_type_start:';
|
|
623
|
+
let typeStopWrapper = ':vscode_type_stop ';
|
|
624
|
+
let type;
|
|
625
|
+
const typeStartIndex = line.indexOf(typeStartWrapper);
|
|
626
|
+
//if the type is present, extract it
|
|
627
|
+
if (typeStartIndex > -1) {
|
|
628
|
+
type = line.substring(typeStartIndex + typeStartWrapper.length, line.indexOf(typeStopWrapper));
|
|
629
|
+
//throw out the type chunk
|
|
630
|
+
line = line.substring(line.indexOf(typeStopWrapper) + typeStopWrapper.length);
|
|
631
|
+
}
|
|
561
632
|
if (line.includes('vscode_is_string:true')) {
|
|
562
633
|
line = line.replace('vscode_is_string:true', '');
|
|
563
634
|
//support multi-line strings
|
|
@@ -590,22 +661,22 @@ class TelnetAdapter {
|
|
|
590
661
|
let collectionEnd;
|
|
591
662
|
if (line.includes('<Component: roList>')) {
|
|
592
663
|
collectionEnd = ')';
|
|
593
|
-
child.highLevelType = HighLevelType.array;
|
|
664
|
+
child.highLevelType = interfaces_1.HighLevelType.array;
|
|
594
665
|
child.type = objectType;
|
|
595
666
|
}
|
|
596
667
|
else if (line.includes('<Component: roArray>')) {
|
|
597
668
|
collectionEnd = ']';
|
|
598
|
-
child.highLevelType = HighLevelType.array;
|
|
669
|
+
child.highLevelType = interfaces_1.HighLevelType.array;
|
|
599
670
|
child.type = this.getObjectType(line);
|
|
600
671
|
}
|
|
601
672
|
else if (line.includes('<Component: roByteArray>')) {
|
|
602
673
|
collectionEnd = ']';
|
|
603
|
-
child.highLevelType = HighLevelType.array;
|
|
674
|
+
child.highLevelType = interfaces_1.HighLevelType.array;
|
|
604
675
|
child.type = this.getObjectType(line);
|
|
605
676
|
}
|
|
606
677
|
else if (line.includes('<Component: roAssociativeArray>') || isRoSGNode) {
|
|
607
678
|
collectionEnd = '}';
|
|
608
|
-
child.highLevelType = HighLevelType.object;
|
|
679
|
+
child.highLevelType = interfaces_1.HighLevelType.object;
|
|
609
680
|
child.type = this.getObjectType(line);
|
|
610
681
|
}
|
|
611
682
|
let collectionLineList = [line];
|
|
@@ -617,53 +688,31 @@ class TelnetAdapter {
|
|
|
617
688
|
break;
|
|
618
689
|
}
|
|
619
690
|
}
|
|
620
|
-
//
|
|
621
|
-
|
|
622
|
-
if
|
|
623
|
-
child.children = [];
|
|
624
|
-
//get the object children
|
|
625
|
-
}
|
|
626
|
-
else if (child.highLevelType === HighLevelType.object) {
|
|
627
|
-
child.children = this.getObjectChildren(child.evaluateName, collectionLineList.join('\n'));
|
|
628
|
-
//get all of the array children right now since we have them
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
child.children = this.getArrayOrListChildren(child.evaluateName, collectionLineList.join('\n'));
|
|
632
|
-
child.type += `(${child.children.length})`;
|
|
633
|
-
}
|
|
634
|
-
if (isRoSGNode) {
|
|
635
|
-
let nodeChildrenProperty = {
|
|
636
|
-
name: '[[children]]',
|
|
637
|
-
type: 'roArray',
|
|
638
|
-
highLevelType: 'array',
|
|
639
|
-
evaluateName: `${child.evaluateName}.getChildren(-1,0)`,
|
|
640
|
-
children: []
|
|
641
|
-
};
|
|
642
|
-
child.children.push(nodeChildrenProperty);
|
|
643
|
-
}
|
|
644
|
-
//this if block must preseec the `line.indexOf('<Component') > -1` line because roInvalid is a component too.
|
|
691
|
+
//we have reached the end of the collection. scrap children because they need evaluated in a separate call to compute their types
|
|
692
|
+
child.children = [];
|
|
693
|
+
//this if block must pre-seek the `line.indexOf('<Component') > -1` line because roInvalid is a component too.
|
|
645
694
|
}
|
|
646
695
|
else if (objectType === 'roInvalid') {
|
|
647
|
-
child.highLevelType = HighLevelType.uninitialized;
|
|
696
|
+
child.highLevelType = interfaces_1.HighLevelType.uninitialized;
|
|
648
697
|
child.type = 'roInvalid';
|
|
649
698
|
child.value = 'roInvalid';
|
|
650
699
|
child.children = undefined;
|
|
651
700
|
}
|
|
652
701
|
else if (line.includes('<Component:')) {
|
|
653
702
|
//handle things like nodes
|
|
654
|
-
child.highLevelType = HighLevelType.object;
|
|
703
|
+
child.highLevelType = interfaces_1.HighLevelType.object;
|
|
655
704
|
child.type = objectType;
|
|
656
705
|
}
|
|
657
706
|
else {
|
|
658
707
|
//is some primative type
|
|
659
|
-
child.type =
|
|
708
|
+
child.type = type;
|
|
660
709
|
child.value = line.trim();
|
|
661
|
-
child.highLevelType = HighLevelType.primative;
|
|
710
|
+
child.highLevelType = interfaces_1.HighLevelType.primative;
|
|
662
711
|
child.children = undefined;
|
|
663
712
|
}
|
|
664
713
|
children.push(child);
|
|
665
714
|
}
|
|
666
|
-
let sortedChildren = natural_orderby_1.orderBy(children, ['name']);
|
|
715
|
+
let sortedChildren = (0, natural_orderby_1.orderBy)(children, ['name']);
|
|
667
716
|
return sortedChildren;
|
|
668
717
|
}
|
|
669
718
|
/**
|
|
@@ -681,7 +730,7 @@ class TelnetAdapter {
|
|
|
681
730
|
}
|
|
682
731
|
let children = [];
|
|
683
732
|
//split by newline. the array contents start at index 2
|
|
684
|
-
let lines =
|
|
733
|
+
let lines = data.split(/\r?\n/g);
|
|
685
734
|
let arrayIndex = 0;
|
|
686
735
|
for (let i = 2; i < lines.length; i++) {
|
|
687
736
|
let line = lines[i].trim();
|
|
@@ -704,7 +753,7 @@ class TelnetAdapter {
|
|
|
704
753
|
else {
|
|
705
754
|
child.type = this.getPrimativeTypeFromValue(line);
|
|
706
755
|
child.value = line;
|
|
707
|
-
child.highLevelType = HighLevelType.primative;
|
|
756
|
+
child.highLevelType = interfaces_1.HighLevelType.primative;
|
|
708
757
|
}
|
|
709
758
|
children.push(child);
|
|
710
759
|
arrayIndex++;
|
|
@@ -733,7 +782,7 @@ class TelnetAdapter {
|
|
|
733
782
|
try {
|
|
734
783
|
let children = [];
|
|
735
784
|
//split by newline. the object contents start at index 2
|
|
736
|
-
let lines =
|
|
785
|
+
let lines = data.split(/\r?\n/g);
|
|
737
786
|
for (let i = 2; i < lines.length; i++) {
|
|
738
787
|
let line = lines[i];
|
|
739
788
|
let trimmedLine = line.trim();
|
|
@@ -749,7 +798,7 @@ class TelnetAdapter {
|
|
|
749
798
|
child = {
|
|
750
799
|
name: line,
|
|
751
800
|
type: '<ERROR>',
|
|
752
|
-
highLevelType: HighLevelType.uninitialized,
|
|
801
|
+
highLevelType: interfaces_1.HighLevelType.uninitialized,
|
|
753
802
|
evaluateName: undefined,
|
|
754
803
|
variablePath: [],
|
|
755
804
|
elementCount: -1,
|
|
@@ -774,7 +823,7 @@ class TelnetAdapter {
|
|
|
774
823
|
else {
|
|
775
824
|
child.type = this.getPrimativeTypeFromValue(trimmedLine);
|
|
776
825
|
child.value = lineParseResult.value;
|
|
777
|
-
child.highLevelType = HighLevelType.primative;
|
|
826
|
+
child.highLevelType = interfaces_1.HighLevelType.primative;
|
|
778
827
|
}
|
|
779
828
|
}
|
|
780
829
|
children.push(child);
|
|
@@ -782,7 +831,7 @@ class TelnetAdapter {
|
|
|
782
831
|
return children;
|
|
783
832
|
}
|
|
784
833
|
catch (e) {
|
|
785
|
-
throw new Error(`Unable to parse BrightScript object: ${e.message}. Data: ${data}`);
|
|
834
|
+
throw new Error(`Unable to parse BrightScript object: ${JSON.stringify(e.message)}. Data: ${data}`);
|
|
786
835
|
}
|
|
787
836
|
}
|
|
788
837
|
/**
|
|
@@ -791,24 +840,24 @@ class TelnetAdapter {
|
|
|
791
840
|
*/
|
|
792
841
|
getHighLevelType(expressionType) {
|
|
793
842
|
if (!expressionType) {
|
|
794
|
-
|
|
843
|
+
return interfaces_1.HighLevelType.unknown;
|
|
795
844
|
}
|
|
796
845
|
expressionType = expressionType.toLowerCase();
|
|
797
846
|
let primativeTypes = ['boolean', 'integer', 'longinteger', 'float', 'double', 'string', 'rostring', 'invalid'];
|
|
798
847
|
if (primativeTypes.includes(expressionType)) {
|
|
799
|
-
return HighLevelType.primative;
|
|
848
|
+
return interfaces_1.HighLevelType.primative;
|
|
800
849
|
}
|
|
801
850
|
else if (expressionType === 'roarray' || expressionType === 'rolist') {
|
|
802
|
-
return HighLevelType.array;
|
|
851
|
+
return interfaces_1.HighLevelType.array;
|
|
803
852
|
}
|
|
804
853
|
else if (expressionType === 'function') {
|
|
805
|
-
return HighLevelType.function;
|
|
854
|
+
return interfaces_1.HighLevelType.function;
|
|
806
855
|
}
|
|
807
856
|
else if (expressionType === '<uninitialized>') {
|
|
808
|
-
return HighLevelType.uninitialized;
|
|
857
|
+
return interfaces_1.HighLevelType.uninitialized;
|
|
809
858
|
}
|
|
810
859
|
else {
|
|
811
|
-
return HighLevelType.object;
|
|
860
|
+
return interfaces_1.HighLevelType.object;
|
|
812
861
|
}
|
|
813
862
|
}
|
|
814
863
|
/**
|
|
@@ -821,17 +870,10 @@ class TelnetAdapter {
|
|
|
821
870
|
}
|
|
822
871
|
expression = `Type(${expression})`;
|
|
823
872
|
return this.resolve(`${expression}`, async () => {
|
|
824
|
-
|
|
825
|
-
let
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
//remove whitespace
|
|
829
|
-
typeValue = typeValue.trim();
|
|
830
|
-
return typeValue;
|
|
831
|
-
}
|
|
832
|
-
else {
|
|
833
|
-
return null;
|
|
834
|
-
}
|
|
873
|
+
var _a;
|
|
874
|
+
let data = await this.requestPipeline.executeCommand(`print ${expression}`, { waitForPrompt: true });
|
|
875
|
+
//remove whitespace
|
|
876
|
+
return (_a = data === null || data === void 0 ? void 0 : data.trim()) !== null && _a !== void 0 ? _a : null;
|
|
835
877
|
});
|
|
836
878
|
}
|
|
837
879
|
/**
|
|
@@ -840,22 +882,34 @@ class TelnetAdapter {
|
|
|
840
882
|
* @param factory
|
|
841
883
|
*/
|
|
842
884
|
resolve(key, factory) {
|
|
843
|
-
|
|
844
|
-
|
|
885
|
+
try {
|
|
886
|
+
if (this.cache[key]) {
|
|
887
|
+
this.logger.debug(`resolve cache "${key}": already exists`);
|
|
888
|
+
return this.cache[key];
|
|
889
|
+
}
|
|
890
|
+
else {
|
|
891
|
+
this.logger.debug(`resolve cache "${key}": calling factory`);
|
|
892
|
+
const result = factory();
|
|
893
|
+
this.cache[key] = Promise.resolve(result);
|
|
894
|
+
return this.cache[key];
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
catch (e) {
|
|
898
|
+
return Promise.reject(e);
|
|
845
899
|
}
|
|
846
|
-
this.cache[key] = Promise.resolve(factory());
|
|
847
|
-
return this.cache[key];
|
|
848
900
|
}
|
|
849
901
|
/**
|
|
850
902
|
* Get a list of threads. The first thread in the list is the active thread
|
|
851
903
|
*/
|
|
852
904
|
async getThreads() {
|
|
905
|
+
this.logger.log('getThreads');
|
|
853
906
|
if (!this.isAtDebuggerPrompt) {
|
|
854
|
-
|
|
907
|
+
this.logger.log('Cannot get threads: debugger is not paused');
|
|
908
|
+
return [];
|
|
855
909
|
}
|
|
856
910
|
return this.resolve('threads', async () => {
|
|
857
|
-
let data = await this.requestPipeline.executeCommand('threads', true);
|
|
858
|
-
let dataString = data.toString();
|
|
911
|
+
let data = await this.requestPipeline.executeCommand('threads', { waitForPrompt: true });
|
|
912
|
+
let dataString = data.toString().trim();
|
|
859
913
|
let matches = /^\s+(\d+\*)\s+(.*)\((\d+)\)\s+(.*)/gm.exec(dataString);
|
|
860
914
|
let threads = [];
|
|
861
915
|
if (matches) {
|
|
@@ -886,9 +940,8 @@ class TelnetAdapter {
|
|
|
886
940
|
/**
|
|
887
941
|
* Disconnect from the telnet session and unset all objects
|
|
888
942
|
*/
|
|
889
|
-
|
|
943
|
+
destroy() {
|
|
890
944
|
if (this.requestPipeline) {
|
|
891
|
-
await this.exitActiveBrightscriptDebugger();
|
|
892
945
|
this.requestPipeline.destroy();
|
|
893
946
|
}
|
|
894
947
|
this.requestPipeline = undefined;
|
|
@@ -897,20 +950,8 @@ class TelnetAdapter {
|
|
|
897
950
|
this.emitter.removeAllListeners();
|
|
898
951
|
}
|
|
899
952
|
this.emitter = undefined;
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
* Make sure any active Brightscript Debugger threads are exited
|
|
903
|
-
*/
|
|
904
|
-
async exitActiveBrightscriptDebugger() {
|
|
905
|
-
if (this.requestPipeline) {
|
|
906
|
-
let commandsExecuted = 0;
|
|
907
|
-
do {
|
|
908
|
-
let data = await this.requestPipeline.executeCommand(`exit`, false);
|
|
909
|
-
// This seems to work without the delay but I wonder about slower devices
|
|
910
|
-
// await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](100);
|
|
911
|
-
commandsExecuted++;
|
|
912
|
-
} while (commandsExecuted < 10);
|
|
913
|
-
}
|
|
953
|
+
//needs to be async to match the DebugProtocolAdapter implementation
|
|
954
|
+
return Promise.resolve();
|
|
914
955
|
}
|
|
915
956
|
// #region Rendezvous Tracker pass though functions
|
|
916
957
|
/**
|
|
@@ -945,14 +986,6 @@ var EventName;
|
|
|
945
986
|
(function (EventName) {
|
|
946
987
|
EventName["suspend"] = "suspend";
|
|
947
988
|
})(EventName = exports.EventName || (exports.EventName = {}));
|
|
948
|
-
var HighLevelType;
|
|
949
|
-
(function (HighLevelType) {
|
|
950
|
-
HighLevelType["primative"] = "primative";
|
|
951
|
-
HighLevelType["array"] = "array";
|
|
952
|
-
HighLevelType["function"] = "function";
|
|
953
|
-
HighLevelType["object"] = "object";
|
|
954
|
-
HighLevelType["uninitialized"] = "uninitialized";
|
|
955
|
-
})(HighLevelType = exports.HighLevelType || (exports.HighLevelType = {}));
|
|
956
989
|
var KeyType;
|
|
957
990
|
(function (KeyType) {
|
|
958
991
|
KeyType["string"] = "String";
|
|
@@ -967,157 +1000,4 @@ var PrimativeType;
|
|
|
967
1000
|
PrimativeType["integer"] = "Integer";
|
|
968
1001
|
PrimativeType["float"] = "Float";
|
|
969
1002
|
})(PrimativeType = exports.PrimativeType || (exports.PrimativeType = {}));
|
|
970
|
-
class RequestPipeline {
|
|
971
|
-
constructor(client) {
|
|
972
|
-
this.client = client;
|
|
973
|
-
this.requests = [];
|
|
974
|
-
this.isAtDebuggerPrompt = false;
|
|
975
|
-
this.currentRequest = undefined;
|
|
976
|
-
this.emitter = new EventEmitter();
|
|
977
|
-
this.debuggerLineRegex = /Brightscript\s+Debugger>\s*$/i;
|
|
978
|
-
this.connect();
|
|
979
|
-
}
|
|
980
|
-
get isProcessing() {
|
|
981
|
-
return this.currentRequest !== undefined;
|
|
982
|
-
}
|
|
983
|
-
get hasRequests() {
|
|
984
|
-
return this.requests.length > 0;
|
|
985
|
-
}
|
|
986
|
-
on(eventName, handler) {
|
|
987
|
-
this.emitter.on(eventName, handler);
|
|
988
|
-
return () => {
|
|
989
|
-
this.emitter.removeListener(eventName, handler);
|
|
990
|
-
};
|
|
991
|
-
}
|
|
992
|
-
emit(eventName, data) {
|
|
993
|
-
this.emitter.emit(eventName, data);
|
|
994
|
-
}
|
|
995
|
-
connect() {
|
|
996
|
-
let allResponseText = '';
|
|
997
|
-
let lastPartialLine = '';
|
|
998
|
-
this.client.addListener('data', (data) => {
|
|
999
|
-
let responseText = data.toString();
|
|
1000
|
-
const cumulative = lastPartialLine + responseText;
|
|
1001
|
-
if (!cumulative.endsWith('\n') && !this.checkForDebuggerPrompt(cumulative)) {
|
|
1002
|
-
// buffer was split and was not the result of a prompt, save the partial line
|
|
1003
|
-
lastPartialLine += responseText;
|
|
1004
|
-
return;
|
|
1005
|
-
}
|
|
1006
|
-
if (lastPartialLine) {
|
|
1007
|
-
// there was leftover lines, join the partial lines back together
|
|
1008
|
-
responseText = lastPartialLine + responseText;
|
|
1009
|
-
lastPartialLine = '';
|
|
1010
|
-
}
|
|
1011
|
-
//forward all raw console output
|
|
1012
|
-
this.emit('console-output', responseText);
|
|
1013
|
-
allResponseText += responseText;
|
|
1014
|
-
let foundDebuggerPrompt = this.checkForDebuggerPrompt(allResponseText);
|
|
1015
|
-
//if we are not processing, immediately broadcast the latest data
|
|
1016
|
-
if (!this.isProcessing) {
|
|
1017
|
-
this.emit('unhandled-console-output', allResponseText);
|
|
1018
|
-
allResponseText = '';
|
|
1019
|
-
if (foundDebuggerPrompt) {
|
|
1020
|
-
this.isAtDebuggerPrompt = true;
|
|
1021
|
-
if (this.hasRequests) {
|
|
1022
|
-
// There are requests waiting to be processed
|
|
1023
|
-
this.process();
|
|
1024
|
-
}
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
else {
|
|
1028
|
-
//if responseText produced a prompt, return the responseText
|
|
1029
|
-
if (foundDebuggerPrompt) {
|
|
1030
|
-
//resolve the command's promise (if it cares)
|
|
1031
|
-
this.isAtDebuggerPrompt = true;
|
|
1032
|
-
this.currentRequest.onComplete(allResponseText);
|
|
1033
|
-
allResponseText = '';
|
|
1034
|
-
this.currentRequest = undefined;
|
|
1035
|
-
//try to run the next request
|
|
1036
|
-
this.process();
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
});
|
|
1040
|
-
}
|
|
1041
|
-
/**
|
|
1042
|
-
* Checks the supplied string for the debugger input prompt
|
|
1043
|
-
* @param responseText
|
|
1044
|
-
*/
|
|
1045
|
-
checkForDebuggerPrompt(responseText) {
|
|
1046
|
-
let match = this.debuggerLineRegex.exec(responseText.trim());
|
|
1047
|
-
return (match);
|
|
1048
|
-
}
|
|
1049
|
-
/**
|
|
1050
|
-
* Schedule a command to be run. Resolves with the result once the command finishes
|
|
1051
|
-
* @param commandFunction
|
|
1052
|
-
* @param waitForPrompt - if true, the promise will wait until we find a prompt, and return all output in between. If false, the promise will immediately resolve
|
|
1053
|
-
* @param forceExecute - if true, it is assumed the command can be run at any time and will be executed immediately
|
|
1054
|
-
* @param silent - if true, the command will be hidden from the output
|
|
1055
|
-
*/
|
|
1056
|
-
executeCommand(command, waitForPrompt, forceExecute = false, silent = false) {
|
|
1057
|
-
console.debug(`Execute command (and ${waitForPrompt ? 'do' : 'do not'} wait for prompt):`, command);
|
|
1058
|
-
return new Promise((resolve, reject) => {
|
|
1059
|
-
let executeCommand = () => {
|
|
1060
|
-
let commandText = `${command}\r\n`;
|
|
1061
|
-
if (!silent) {
|
|
1062
|
-
this.emit('console-output', command);
|
|
1063
|
-
}
|
|
1064
|
-
this.client.write(commandText);
|
|
1065
|
-
if (waitForPrompt) {
|
|
1066
|
-
// The act of executing this command means we are no longer at the debug prompt
|
|
1067
|
-
this.isAtDebuggerPrompt = false;
|
|
1068
|
-
}
|
|
1069
|
-
};
|
|
1070
|
-
let request = {
|
|
1071
|
-
executeCommand: executeCommand,
|
|
1072
|
-
onComplete: (data) => {
|
|
1073
|
-
console.debug(`Command finished (${waitForPrompt ? 'after waiting for prompt' : 'did not wait for prompt'}`, command);
|
|
1074
|
-
console.debug('Data:', data);
|
|
1075
|
-
resolve(data);
|
|
1076
|
-
},
|
|
1077
|
-
waitForPrompt: waitForPrompt
|
|
1078
|
-
};
|
|
1079
|
-
if (!waitForPrompt) {
|
|
1080
|
-
if (!this.isProcessing || forceExecute) {
|
|
1081
|
-
//fire and forget the command
|
|
1082
|
-
request.executeCommand();
|
|
1083
|
-
//the command doesn't care about the output, resolve it immediately
|
|
1084
|
-
request.onComplete(undefined);
|
|
1085
|
-
}
|
|
1086
|
-
else {
|
|
1087
|
-
// Skip this request as the device is not ready to accept the command or it can not be run at any time
|
|
1088
|
-
}
|
|
1089
|
-
}
|
|
1090
|
-
else {
|
|
1091
|
-
this.requests.push(request);
|
|
1092
|
-
if (this.isAtDebuggerPrompt) {
|
|
1093
|
-
//start processing since we are already at a debug prompt (safe to call multiple times)
|
|
1094
|
-
this.process();
|
|
1095
|
-
}
|
|
1096
|
-
else {
|
|
1097
|
-
// do not run the command until the device is at a debug prompt.
|
|
1098
|
-
// this will be detected in the data listener in the connect function
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
});
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Internal request processing function
|
|
1105
|
-
*/
|
|
1106
|
-
process() {
|
|
1107
|
-
if (this.isProcessing || !this.hasRequests) {
|
|
1108
|
-
return;
|
|
1109
|
-
}
|
|
1110
|
-
//get the oldest command
|
|
1111
|
-
let nextRequest = this.requests.shift();
|
|
1112
|
-
this.currentRequest = nextRequest;
|
|
1113
|
-
//run the request. the data listener will handle launching the next request once this one has finished processing
|
|
1114
|
-
nextRequest.executeCommand();
|
|
1115
|
-
}
|
|
1116
|
-
destroy() {
|
|
1117
|
-
this.client.removeAllListeners();
|
|
1118
|
-
this.client.destroy();
|
|
1119
|
-
this.client = undefined;
|
|
1120
|
-
}
|
|
1121
|
-
}
|
|
1122
|
-
exports.RequestPipeline = RequestPipeline;
|
|
1123
1003
|
//# sourceMappingURL=TelnetAdapter.js.map
|