roku-debug 0.8.5 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +36 -2
- package/dist/CompileErrorProcessor.d.ts +1 -0
- package/dist/CompileErrorProcessor.js +5 -3
- package/dist/CompileErrorProcessor.js.map +1 -1
- package/dist/LaunchConfiguration.d.ts +5 -0
- package/dist/SceneGraphDebugCommandController.d.ts +1 -0
- package/dist/SceneGraphDebugCommandController.js +11 -5
- package/dist/SceneGraphDebugCommandController.js.map +1 -1
- package/dist/adapters/DebugProtocolAdapter.d.ts +3 -11
- package/dist/adapters/DebugProtocolAdapter.js +32 -28
- package/dist/adapters/DebugProtocolAdapter.js.map +1 -1
- package/dist/adapters/TelnetAdapter.d.ts +10 -57
- package/dist/adapters/TelnetAdapter.js +230 -379
- 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/Debugger.d.ts +1 -0
- package/dist/debugProtocol/Debugger.js +20 -19
- package/dist/debugProtocol/Debugger.js.map +1 -1
- package/dist/debugProtocol/responses/VariableResponse.d.ts +1 -1
- package/dist/debugProtocol/responses/VariableResponse.js.map +1 -1
- package/dist/debugSession/BrightScriptDebugSession.d.ts +6 -0
- package/dist/debugSession/BrightScriptDebugSession.js +91 -40
- package/dist/debugSession/BrightScriptDebugSession.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/FileManager.d.ts +1 -0
- package/dist/managers/FileManager.js +3 -1
- package/dist/managers/FileManager.js.map +1 -1
- package/dist/managers/ProjectManager.d.ts +2 -0
- package/dist/managers/ProjectManager.js +5 -2
- package/dist/managers/ProjectManager.js.map +1 -1
- package/dist/managers/SourceMapManager.d.ts +1 -0
- package/dist/managers/SourceMapManager.js +5 -5
- package/dist/managers/SourceMapManager.js.map +1 -1
- package/dist/util.d.ts +34 -4
- package/dist/util.js +94 -28
- package/dist/util.js.map +1 -1
- package/package.json +5 -2
- package/roku-debug-0.9.1.tgz +0 -0
- package/roku-debug-0.8.5.tgz +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
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
8
|
const PrintedObjectParser_1 = require("../PrintedObjectParser");
|
|
9
9
|
const CompileErrorProcessor_1 = require("../CompileErrorProcessor");
|
|
10
10
|
const RendezvousTracker_1 = require("../RendezvousTracker");
|
|
11
11
|
const ChanperfTracker_1 = require("../ChanperfTracker");
|
|
12
12
|
const util_1 = require("../util");
|
|
13
|
+
const logging_1 = require("../logging");
|
|
14
|
+
const interfaces_1 = require("../interfaces");
|
|
15
|
+
const TelnetRequestPipeline_1 = require("./TelnetRequestPipeline");
|
|
13
16
|
/**
|
|
14
17
|
* A class that connects to a Roku device over telnet debugger port and provides a standardized way of interacting with it.
|
|
15
18
|
*/
|
|
@@ -17,6 +20,7 @@ class TelnetAdapter {
|
|
|
17
20
|
constructor(host, enableDebuggerAutoRecovery = false) {
|
|
18
21
|
this.host = host;
|
|
19
22
|
this.enableDebuggerAutoRecovery = enableDebuggerAutoRecovery;
|
|
23
|
+
this.logger = logging_1.logger.createLogger(`[${TelnetAdapter.name}]`);
|
|
20
24
|
this.isNextBreakpointSkipped = false;
|
|
21
25
|
this.cache = {};
|
|
22
26
|
this.supportsMultipleRuns = true;
|
|
@@ -71,6 +75,7 @@ class TelnetAdapter {
|
|
|
71
75
|
}, 0);
|
|
72
76
|
}
|
|
73
77
|
async activate() {
|
|
78
|
+
this.logger.log('Activate TelnetAdapter');
|
|
74
79
|
this.isActivated = true;
|
|
75
80
|
await this.handleStartupIfReady();
|
|
76
81
|
}
|
|
@@ -78,14 +83,17 @@ class TelnetAdapter {
|
|
|
78
83
|
await this.compileErrorProcessor.sendErrors();
|
|
79
84
|
}
|
|
80
85
|
async handleStartupIfReady() {
|
|
86
|
+
var _a;
|
|
81
87
|
if (this.isActivated && this.isAppRunning) {
|
|
88
|
+
this.logger.log('Handling startup');
|
|
82
89
|
this.emit('start');
|
|
83
90
|
//if we are already sitting at a debugger prompt, we need to emit the first suspend event.
|
|
84
91
|
//If not, then there are probably still messages being received, so let the normal handler
|
|
85
92
|
//emit the suspend event when it's ready
|
|
86
93
|
if (this.isAtDebuggerPrompt === true) {
|
|
94
|
+
this.logger.log(`At debug prompt, so trigger the 'suspend' event`);
|
|
87
95
|
let threads = await this.getThreads();
|
|
88
|
-
this.emit('suspend', threads[0].threadId);
|
|
96
|
+
this.emit('suspend', (_a = threads[0]) === null || _a === void 0 ? void 0 : _a.threadId);
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
}
|
|
@@ -96,19 +104,22 @@ class TelnetAdapter {
|
|
|
96
104
|
* @param maxWaitMilliseconds
|
|
97
105
|
*/
|
|
98
106
|
settle(client, name, maxWaitMilliseconds = 400) {
|
|
107
|
+
const startTime = new Date();
|
|
108
|
+
this.logger.log('Waiting for telnet client to settle');
|
|
99
109
|
return new Promise((resolve) => {
|
|
100
110
|
let callCount = -1;
|
|
101
|
-
|
|
111
|
+
const handler = () => {
|
|
102
112
|
callCount++;
|
|
103
113
|
let myCallCount = callCount;
|
|
104
114
|
setTimeout(() => {
|
|
105
115
|
//if no other calls have been made since the timeout started, then the listener has settled
|
|
106
116
|
if (myCallCount === callCount) {
|
|
107
117
|
client.removeListener(name, handler);
|
|
118
|
+
this.logger.log(`Telnet client has settled after ${new Date().getTime() - startTime.getTime()} milliseconds`);
|
|
108
119
|
resolve(callCount);
|
|
109
120
|
}
|
|
110
121
|
}, maxWaitMilliseconds);
|
|
111
|
-
}
|
|
122
|
+
};
|
|
112
123
|
client.addListener(name, handler);
|
|
113
124
|
//call the handler immediately so we have a timeout
|
|
114
125
|
handler();
|
|
@@ -134,35 +145,38 @@ class TelnetAdapter {
|
|
|
134
145
|
}
|
|
135
146
|
}
|
|
136
147
|
}
|
|
137
|
-
return text;
|
|
138
148
|
}
|
|
139
149
|
/**
|
|
140
150
|
* Connect to the telnet session. This should be called before the channel is launched.
|
|
141
151
|
*/
|
|
142
152
|
async connect() {
|
|
153
|
+
this.logger.log('Establishing telnet connection');
|
|
143
154
|
let deferred = (0, util_1.defer)();
|
|
144
155
|
this.isInMicroDebugger = false;
|
|
145
156
|
this.isNextBreakpointSkipped = false;
|
|
146
157
|
try {
|
|
158
|
+
this.logger.log('Pressing home button');
|
|
147
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
|
|
148
160
|
await rokuDeploy.pressHomeButton(this.host);
|
|
149
|
-
let client = new
|
|
150
|
-
client.connect(8085, this.host, () => {
|
|
151
|
-
console.log(`+++++++++++ CONNECTED TO DEVICE ${this.host} +++++++++++`);
|
|
152
|
-
this.connected = true;
|
|
153
|
-
this.emit('connected', this.connected);
|
|
154
|
-
});
|
|
161
|
+
let client = new net_1.Socket();
|
|
155
162
|
//listen for the close event
|
|
156
163
|
client.addListener('close', (err, data) => {
|
|
157
164
|
this.emit('close');
|
|
158
165
|
});
|
|
159
166
|
//if the connection fails, reject the connect promise
|
|
160
167
|
client.addListener('error', (err) => {
|
|
161
|
-
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} `));
|
|
169
|
+
});
|
|
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);
|
|
162
175
|
});
|
|
163
|
-
await
|
|
176
|
+
await settlePromise;
|
|
164
177
|
//hook up the pipeline to the socket
|
|
165
|
-
this.requestPipeline = new
|
|
178
|
+
this.requestPipeline = new TelnetRequestPipeline_1.TelnetRequestPipeline(client);
|
|
179
|
+
this.requestPipeline.connect();
|
|
166
180
|
//forward all raw console output
|
|
167
181
|
this.requestPipeline.on('console-output', (output) => {
|
|
168
182
|
this.processBreakpoints(output);
|
|
@@ -188,13 +202,14 @@ class TelnetAdapter {
|
|
|
188
202
|
}
|
|
189
203
|
// short circuit after the output has been sent as console output
|
|
190
204
|
if (hasRuntimeError) {
|
|
191
|
-
|
|
205
|
+
this.logger.log('Detected runtime error in output', { responseText });
|
|
192
206
|
this.isAtDebuggerPrompt = true;
|
|
193
207
|
return;
|
|
194
208
|
}
|
|
195
|
-
this.processUnhandledLines(responseText);
|
|
209
|
+
this.compileErrorProcessor.processUnhandledLines(responseText);
|
|
196
210
|
let match;
|
|
197
211
|
if (this.isAtCannotContinue(responseText)) {
|
|
212
|
+
this.logger.log('is at cannot continue');
|
|
198
213
|
this.isAtDebuggerPrompt = true;
|
|
199
214
|
return;
|
|
200
215
|
}
|
|
@@ -203,6 +218,7 @@ class TelnetAdapter {
|
|
|
203
218
|
// eslint-disable-next-line no-cond-assign
|
|
204
219
|
if (match = /\[scrpt.ctx.run.enter\]/i.exec(responseText.trim())) {
|
|
205
220
|
this.isAppRunning = true;
|
|
221
|
+
this.logger.log('Running beacon detected', { responseText });
|
|
206
222
|
void this.handleStartupIfReady();
|
|
207
223
|
}
|
|
208
224
|
//watch for the end of the program
|
|
@@ -211,26 +227,29 @@ class TelnetAdapter {
|
|
|
211
227
|
this.beginAppExit();
|
|
212
228
|
}
|
|
213
229
|
//watch for debugger prompt output
|
|
214
|
-
|
|
215
|
-
|
|
230
|
+
if (util_1.util.endsWithDebuggerPrompt(responseText)) {
|
|
231
|
+
this.logger.log('Debugger prompt detected in', { responseText });
|
|
216
232
|
//if we are activated AND this is the first time seeing the debugger prompt since a continue/step action
|
|
217
233
|
if (this.isNextBreakpointSkipped) {
|
|
218
|
-
|
|
234
|
+
this.logger.log('This debugger is flagged to be skipped');
|
|
219
235
|
this.isInMicroDebugger = false;
|
|
220
236
|
this.isNextBreakpointSkipped = false;
|
|
221
|
-
void this.requestPipeline.executeCommand('c',
|
|
237
|
+
void this.requestPipeline.executeCommand('c', { waitForPrompt: false, insertAtFront: true });
|
|
222
238
|
}
|
|
223
239
|
else {
|
|
224
240
|
if (this.isActivated && this.isAtDebuggerPrompt === false) {
|
|
225
241
|
this.isAtDebuggerPrompt = true;
|
|
242
|
+
this.logger.log('Sending the "suspend" event to the client');
|
|
226
243
|
this.emit('suspend');
|
|
227
244
|
}
|
|
228
245
|
else {
|
|
246
|
+
this.logger.log('Skipping "suspend" event because we are already suspended');
|
|
229
247
|
this.isAtDebuggerPrompt = true;
|
|
230
248
|
}
|
|
231
249
|
}
|
|
232
250
|
}
|
|
233
251
|
else {
|
|
252
|
+
this.logger.debug('responseText does not end with debugger prompt. isAtDebuggerPrompt = false', { responseText });
|
|
234
253
|
this.isAtDebuggerPrompt = false;
|
|
235
254
|
}
|
|
236
255
|
}
|
|
@@ -244,7 +263,9 @@ class TelnetAdapter {
|
|
|
244
263
|
return deferred.promise;
|
|
245
264
|
}
|
|
246
265
|
beginAppExit() {
|
|
266
|
+
this.logger.log('Beginning app exit');
|
|
247
267
|
this.compileErrorProcessor.compileErrorTimer = setTimeout(() => {
|
|
268
|
+
this.logger.info('emitting app-exit');
|
|
248
269
|
this.isAppRunning = false;
|
|
249
270
|
this.emit('app-exit');
|
|
250
271
|
}, 200);
|
|
@@ -285,43 +306,46 @@ class TelnetAdapter {
|
|
|
285
306
|
return false;
|
|
286
307
|
}
|
|
287
308
|
}
|
|
288
|
-
processUnhandledLines(responseText) {
|
|
289
|
-
this.compileErrorProcessor.processUnhandledLines(responseText);
|
|
290
|
-
}
|
|
291
309
|
/**
|
|
292
310
|
* Send command to step over
|
|
293
311
|
*/
|
|
294
312
|
stepOver() {
|
|
313
|
+
this.logger.log('stepOver');
|
|
295
314
|
this.clearCache();
|
|
296
|
-
return this.requestPipeline.executeCommand('over', false);
|
|
315
|
+
return this.requestPipeline.executeCommand('over', { waitForPrompt: false, insertAtFront: true });
|
|
297
316
|
}
|
|
298
317
|
stepInto() {
|
|
318
|
+
this.logger.log('stepInto');
|
|
299
319
|
this.clearCache();
|
|
300
|
-
return this.requestPipeline.executeCommand('step', false);
|
|
320
|
+
return this.requestPipeline.executeCommand('step', { waitForPrompt: false, insertAtFront: true });
|
|
301
321
|
}
|
|
302
322
|
stepOut() {
|
|
323
|
+
this.logger.log('stepOut');
|
|
303
324
|
this.clearCache();
|
|
304
|
-
return this.requestPipeline.executeCommand('out', false);
|
|
325
|
+
return this.requestPipeline.executeCommand('out', { waitForPrompt: false, insertAtFront: true });
|
|
305
326
|
}
|
|
306
327
|
/**
|
|
307
328
|
* Tell the brightscript program to continue (i.e. resume program)
|
|
308
329
|
*/
|
|
309
330
|
continue() {
|
|
331
|
+
this.logger.log('continue');
|
|
310
332
|
this.clearCache();
|
|
311
|
-
return this.requestPipeline.executeCommand('c', false);
|
|
333
|
+
return this.requestPipeline.executeCommand('c', { waitForPrompt: false, insertAtFront: true });
|
|
312
334
|
}
|
|
313
335
|
/**
|
|
314
336
|
* Tell the brightscript program to pause (fall into debug mode)
|
|
315
337
|
*/
|
|
316
338
|
pause() {
|
|
339
|
+
this.logger.log('pause');
|
|
317
340
|
this.clearCache();
|
|
318
|
-
//send the kill signal, which breaks into debugger mode
|
|
319
|
-
|
|
341
|
+
//send the kill signal, which breaks into debugger mode. This gets written immediately, regardless of debugger prompt status.
|
|
342
|
+
this.requestPipeline.write('\x03;');
|
|
320
343
|
}
|
|
321
344
|
/**
|
|
322
345
|
* Clears the state, which means that everything will be retrieved fresh next time it is requested
|
|
323
346
|
*/
|
|
324
347
|
clearCache() {
|
|
348
|
+
this.logger.info('Clearing TelnetAdapter cache');
|
|
325
349
|
this.cache = {};
|
|
326
350
|
this.isAtDebuggerPrompt = false;
|
|
327
351
|
}
|
|
@@ -330,24 +354,26 @@ class TelnetAdapter {
|
|
|
330
354
|
* @param command the command to execute. If the command does not start with `print` the command will be prefixed with `print ` because
|
|
331
355
|
*/
|
|
332
356
|
async evaluate(command) {
|
|
357
|
+
this.logger.log('evaluate ', { command });
|
|
333
358
|
if (!this.isAtDebuggerPrompt) {
|
|
334
359
|
throw new Error('Cannot run evaluate: debugger is not paused');
|
|
335
360
|
}
|
|
336
361
|
//clear the cache (we don't know what command the user entered)
|
|
337
362
|
this.clearCache();
|
|
338
363
|
//don't wait for the output...we don't know what command the user entered
|
|
339
|
-
let responseText = await this.requestPipeline.executeCommand(command, true);
|
|
364
|
+
let responseText = await this.requestPipeline.executeCommand(command, { waitForPrompt: true });
|
|
340
365
|
//we know that if we got a response, we are back at a debugger prompt
|
|
341
366
|
this.isAtDebuggerPrompt = true;
|
|
342
367
|
return responseText;
|
|
343
368
|
}
|
|
344
369
|
async getStackTrace() {
|
|
370
|
+
this.logger.log(TelnetAdapter.prototype.getStackTrace.name);
|
|
345
371
|
if (!this.isAtDebuggerPrompt) {
|
|
346
372
|
throw new Error('Cannot get stack trace: debugger is not paused');
|
|
347
373
|
}
|
|
348
374
|
return this.resolve('stackTrace', async () => {
|
|
349
375
|
//perform a request to load the stack trace
|
|
350
|
-
let responseText = await this.requestPipeline.executeCommand('bt', true);
|
|
376
|
+
let responseText = (await this.requestPipeline.executeCommand('bt', { waitForPrompt: true })).trim();
|
|
351
377
|
let regexp = /#(\d+)\s+(?:function|sub)\s+([\$\w\d]+).*\s+file\/line:\s+(.*)\((\d+)\)/ig;
|
|
352
378
|
let matches;
|
|
353
379
|
let frames = [];
|
|
@@ -374,16 +400,6 @@ class TelnetAdapter {
|
|
|
374
400
|
return frames;
|
|
375
401
|
});
|
|
376
402
|
}
|
|
377
|
-
/**
|
|
378
|
-
* Runs a regex to get the content between telnet commands
|
|
379
|
-
* @param value
|
|
380
|
-
*/
|
|
381
|
-
getExpressionDetails(value) {
|
|
382
|
-
const match = /(.*?)\r?\nBrightscript Debugger>\s*/is.exec(value);
|
|
383
|
-
if (match) {
|
|
384
|
-
return match[1];
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
403
|
/**
|
|
388
404
|
* Runs a regex to check if the target is an object and get the type if it is
|
|
389
405
|
* @param value
|
|
@@ -409,14 +425,15 @@ class TelnetAdapter {
|
|
|
409
425
|
* @param scope
|
|
410
426
|
*/
|
|
411
427
|
async getScopeVariables(scope) {
|
|
428
|
+
this.logger.log('getScopeVariables', { scope });
|
|
412
429
|
if (!this.isAtDebuggerPrompt) {
|
|
413
430
|
throw new Error('Cannot resolve variable: debugger is not paused');
|
|
414
431
|
}
|
|
415
432
|
return this.resolve(`Scope Variables`, async () => {
|
|
416
433
|
let data;
|
|
417
434
|
let vars = [];
|
|
418
|
-
data = await this.requestPipeline.executeCommand(`var`, true);
|
|
419
|
-
let splitData = data.split('\n');
|
|
435
|
+
data = await this.requestPipeline.executeCommand(`var`, { waitForPrompt: true });
|
|
436
|
+
let splitData = data.trim().split('\n');
|
|
420
437
|
for (const line of splitData) {
|
|
421
438
|
let match;
|
|
422
439
|
if (!line.includes('Brightscript Debugger') && (match = this.getFirstWord(line))) {
|
|
@@ -435,122 +452,141 @@ class TelnetAdapter {
|
|
|
435
452
|
* @param expression
|
|
436
453
|
*/
|
|
437
454
|
async getVariable(expression) {
|
|
455
|
+
const logger = this.logger.createLogger('[getVariable]');
|
|
456
|
+
logger.info('begin', { expression });
|
|
438
457
|
if (!this.isAtDebuggerPrompt) {
|
|
439
458
|
throw new Error('Cannot resolve variable: debugger is not paused');
|
|
440
459
|
}
|
|
441
|
-
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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;
|
|
554
590
|
}
|
|
555
591
|
/**
|
|
556
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.
|
|
@@ -625,22 +661,22 @@ class TelnetAdapter {
|
|
|
625
661
|
let collectionEnd;
|
|
626
662
|
if (line.includes('<Component: roList>')) {
|
|
627
663
|
collectionEnd = ')';
|
|
628
|
-
child.highLevelType = HighLevelType.array;
|
|
664
|
+
child.highLevelType = interfaces_1.HighLevelType.array;
|
|
629
665
|
child.type = objectType;
|
|
630
666
|
}
|
|
631
667
|
else if (line.includes('<Component: roArray>')) {
|
|
632
668
|
collectionEnd = ']';
|
|
633
|
-
child.highLevelType = HighLevelType.array;
|
|
669
|
+
child.highLevelType = interfaces_1.HighLevelType.array;
|
|
634
670
|
child.type = this.getObjectType(line);
|
|
635
671
|
}
|
|
636
672
|
else if (line.includes('<Component: roByteArray>')) {
|
|
637
673
|
collectionEnd = ']';
|
|
638
|
-
child.highLevelType = HighLevelType.array;
|
|
674
|
+
child.highLevelType = interfaces_1.HighLevelType.array;
|
|
639
675
|
child.type = this.getObjectType(line);
|
|
640
676
|
}
|
|
641
677
|
else if (line.includes('<Component: roAssociativeArray>') || isRoSGNode) {
|
|
642
678
|
collectionEnd = '}';
|
|
643
|
-
child.highLevelType = HighLevelType.object;
|
|
679
|
+
child.highLevelType = interfaces_1.HighLevelType.object;
|
|
644
680
|
child.type = this.getObjectType(line);
|
|
645
681
|
}
|
|
646
682
|
let collectionLineList = [line];
|
|
@@ -654,34 +690,24 @@ class TelnetAdapter {
|
|
|
654
690
|
}
|
|
655
691
|
//we have reached the end of the collection. scrap children because they need evaluated in a separate call to compute their types
|
|
656
692
|
child.children = [];
|
|
657
|
-
if (isRoSGNode) {
|
|
658
|
-
let nodeChildrenProperty = {
|
|
659
|
-
name: '[[children]]',
|
|
660
|
-
type: 'roArray',
|
|
661
|
-
highLevelType: 'array',
|
|
662
|
-
evaluateName: `${child.evaluateName}.getChildren(-1,0)`,
|
|
663
|
-
children: []
|
|
664
|
-
};
|
|
665
|
-
child.children.push(nodeChildrenProperty);
|
|
666
|
-
}
|
|
667
693
|
//this if block must pre-seek the `line.indexOf('<Component') > -1` line because roInvalid is a component too.
|
|
668
694
|
}
|
|
669
695
|
else if (objectType === 'roInvalid') {
|
|
670
|
-
child.highLevelType = HighLevelType.uninitialized;
|
|
696
|
+
child.highLevelType = interfaces_1.HighLevelType.uninitialized;
|
|
671
697
|
child.type = 'roInvalid';
|
|
672
698
|
child.value = 'roInvalid';
|
|
673
699
|
child.children = undefined;
|
|
674
700
|
}
|
|
675
701
|
else if (line.includes('<Component:')) {
|
|
676
702
|
//handle things like nodes
|
|
677
|
-
child.highLevelType = HighLevelType.object;
|
|
703
|
+
child.highLevelType = interfaces_1.HighLevelType.object;
|
|
678
704
|
child.type = objectType;
|
|
679
705
|
}
|
|
680
706
|
else {
|
|
681
707
|
//is some primative type
|
|
682
708
|
child.type = type;
|
|
683
709
|
child.value = line.trim();
|
|
684
|
-
child.highLevelType = HighLevelType.primative;
|
|
710
|
+
child.highLevelType = interfaces_1.HighLevelType.primative;
|
|
685
711
|
child.children = undefined;
|
|
686
712
|
}
|
|
687
713
|
children.push(child);
|
|
@@ -727,7 +753,7 @@ class TelnetAdapter {
|
|
|
727
753
|
else {
|
|
728
754
|
child.type = this.getPrimativeTypeFromValue(line);
|
|
729
755
|
child.value = line;
|
|
730
|
-
child.highLevelType = HighLevelType.primative;
|
|
756
|
+
child.highLevelType = interfaces_1.HighLevelType.primative;
|
|
731
757
|
}
|
|
732
758
|
children.push(child);
|
|
733
759
|
arrayIndex++;
|
|
@@ -772,7 +798,7 @@ class TelnetAdapter {
|
|
|
772
798
|
child = {
|
|
773
799
|
name: line,
|
|
774
800
|
type: '<ERROR>',
|
|
775
|
-
highLevelType: HighLevelType.uninitialized,
|
|
801
|
+
highLevelType: interfaces_1.HighLevelType.uninitialized,
|
|
776
802
|
evaluateName: undefined,
|
|
777
803
|
variablePath: [],
|
|
778
804
|
elementCount: -1,
|
|
@@ -797,7 +823,7 @@ class TelnetAdapter {
|
|
|
797
823
|
else {
|
|
798
824
|
child.type = this.getPrimativeTypeFromValue(trimmedLine);
|
|
799
825
|
child.value = lineParseResult.value;
|
|
800
|
-
child.highLevelType = HighLevelType.primative;
|
|
826
|
+
child.highLevelType = interfaces_1.HighLevelType.primative;
|
|
801
827
|
}
|
|
802
828
|
}
|
|
803
829
|
children.push(child);
|
|
@@ -805,7 +831,7 @@ class TelnetAdapter {
|
|
|
805
831
|
return children;
|
|
806
832
|
}
|
|
807
833
|
catch (e) {
|
|
808
|
-
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}`);
|
|
809
835
|
}
|
|
810
836
|
}
|
|
811
837
|
/**
|
|
@@ -814,24 +840,24 @@ class TelnetAdapter {
|
|
|
814
840
|
*/
|
|
815
841
|
getHighLevelType(expressionType) {
|
|
816
842
|
if (!expressionType) {
|
|
817
|
-
|
|
843
|
+
return interfaces_1.HighLevelType.unknown;
|
|
818
844
|
}
|
|
819
845
|
expressionType = expressionType.toLowerCase();
|
|
820
846
|
let primativeTypes = ['boolean', 'integer', 'longinteger', 'float', 'double', 'string', 'rostring', 'invalid'];
|
|
821
847
|
if (primativeTypes.includes(expressionType)) {
|
|
822
|
-
return HighLevelType.primative;
|
|
848
|
+
return interfaces_1.HighLevelType.primative;
|
|
823
849
|
}
|
|
824
850
|
else if (expressionType === 'roarray' || expressionType === 'rolist') {
|
|
825
|
-
return HighLevelType.array;
|
|
851
|
+
return interfaces_1.HighLevelType.array;
|
|
826
852
|
}
|
|
827
853
|
else if (expressionType === 'function') {
|
|
828
|
-
return HighLevelType.function;
|
|
854
|
+
return interfaces_1.HighLevelType.function;
|
|
829
855
|
}
|
|
830
856
|
else if (expressionType === '<uninitialized>') {
|
|
831
|
-
return HighLevelType.uninitialized;
|
|
857
|
+
return interfaces_1.HighLevelType.uninitialized;
|
|
832
858
|
}
|
|
833
859
|
else {
|
|
834
|
-
return HighLevelType.object;
|
|
860
|
+
return interfaces_1.HighLevelType.object;
|
|
835
861
|
}
|
|
836
862
|
}
|
|
837
863
|
/**
|
|
@@ -844,17 +870,10 @@ class TelnetAdapter {
|
|
|
844
870
|
}
|
|
845
871
|
expression = `Type(${expression})`;
|
|
846
872
|
return this.resolve(`${expression}`, async () => {
|
|
847
|
-
|
|
848
|
-
let
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
//remove whitespace
|
|
852
|
-
typeValue = typeValue.trim();
|
|
853
|
-
return typeValue;
|
|
854
|
-
}
|
|
855
|
-
else {
|
|
856
|
-
return null;
|
|
857
|
-
}
|
|
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;
|
|
858
877
|
});
|
|
859
878
|
}
|
|
860
879
|
/**
|
|
@@ -865,11 +884,15 @@ class TelnetAdapter {
|
|
|
865
884
|
resolve(key, factory) {
|
|
866
885
|
try {
|
|
867
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);
|
|
868
894
|
return this.cache[key];
|
|
869
895
|
}
|
|
870
|
-
const result = factory();
|
|
871
|
-
this.cache[key] = Promise.resolve(result);
|
|
872
|
-
return this.cache[key];
|
|
873
896
|
}
|
|
874
897
|
catch (e) {
|
|
875
898
|
return Promise.reject(e);
|
|
@@ -879,12 +902,14 @@ class TelnetAdapter {
|
|
|
879
902
|
* Get a list of threads. The first thread in the list is the active thread
|
|
880
903
|
*/
|
|
881
904
|
async getThreads() {
|
|
905
|
+
this.logger.log('getThreads');
|
|
882
906
|
if (!this.isAtDebuggerPrompt) {
|
|
883
|
-
|
|
907
|
+
this.logger.log('Cannot get threads: debugger is not paused');
|
|
908
|
+
return [];
|
|
884
909
|
}
|
|
885
910
|
return this.resolve('threads', async () => {
|
|
886
|
-
let data = await this.requestPipeline.executeCommand('threads', true);
|
|
887
|
-
let dataString = data.toString();
|
|
911
|
+
let data = await this.requestPipeline.executeCommand('threads', { waitForPrompt: true });
|
|
912
|
+
let dataString = data.toString().trim();
|
|
888
913
|
let matches = /^\s+(\d+\*)\s+(.*)\((\d+)\)\s+(.*)/gm.exec(dataString);
|
|
889
914
|
let threads = [];
|
|
890
915
|
if (matches) {
|
|
@@ -915,9 +940,8 @@ class TelnetAdapter {
|
|
|
915
940
|
/**
|
|
916
941
|
* Disconnect from the telnet session and unset all objects
|
|
917
942
|
*/
|
|
918
|
-
|
|
943
|
+
destroy() {
|
|
919
944
|
if (this.requestPipeline) {
|
|
920
|
-
await this.exitActiveBrightscriptDebugger();
|
|
921
945
|
this.requestPipeline.destroy();
|
|
922
946
|
}
|
|
923
947
|
this.requestPipeline = undefined;
|
|
@@ -926,20 +950,8 @@ class TelnetAdapter {
|
|
|
926
950
|
this.emitter.removeAllListeners();
|
|
927
951
|
}
|
|
928
952
|
this.emitter = undefined;
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
* Make sure any active Brightscript Debugger threads are exited
|
|
932
|
-
*/
|
|
933
|
-
async exitActiveBrightscriptDebugger() {
|
|
934
|
-
if (this.requestPipeline) {
|
|
935
|
-
let commandsExecuted = 0;
|
|
936
|
-
do {
|
|
937
|
-
let data = await this.requestPipeline.executeCommand(`exit`, false);
|
|
938
|
-
// This seems to work without the delay but I wonder about slower devices
|
|
939
|
-
// await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](100);
|
|
940
|
-
commandsExecuted++;
|
|
941
|
-
} while (commandsExecuted < 10);
|
|
942
|
-
}
|
|
953
|
+
//needs to be async to match the DebugProtocolAdapter implementation
|
|
954
|
+
return Promise.resolve();
|
|
943
955
|
}
|
|
944
956
|
// #region Rendezvous Tracker pass though functions
|
|
945
957
|
/**
|
|
@@ -974,14 +986,6 @@ var EventName;
|
|
|
974
986
|
(function (EventName) {
|
|
975
987
|
EventName["suspend"] = "suspend";
|
|
976
988
|
})(EventName = exports.EventName || (exports.EventName = {}));
|
|
977
|
-
var HighLevelType;
|
|
978
|
-
(function (HighLevelType) {
|
|
979
|
-
HighLevelType["primative"] = "primative";
|
|
980
|
-
HighLevelType["array"] = "array";
|
|
981
|
-
HighLevelType["function"] = "function";
|
|
982
|
-
HighLevelType["object"] = "object";
|
|
983
|
-
HighLevelType["uninitialized"] = "uninitialized";
|
|
984
|
-
})(HighLevelType = exports.HighLevelType || (exports.HighLevelType = {}));
|
|
985
989
|
var KeyType;
|
|
986
990
|
(function (KeyType) {
|
|
987
991
|
KeyType["string"] = "String";
|
|
@@ -996,157 +1000,4 @@ var PrimativeType;
|
|
|
996
1000
|
PrimativeType["integer"] = "Integer";
|
|
997
1001
|
PrimativeType["float"] = "Float";
|
|
998
1002
|
})(PrimativeType = exports.PrimativeType || (exports.PrimativeType = {}));
|
|
999
|
-
class RequestPipeline {
|
|
1000
|
-
constructor(client) {
|
|
1001
|
-
this.client = client;
|
|
1002
|
-
this.requests = [];
|
|
1003
|
-
this.isAtDebuggerPrompt = false;
|
|
1004
|
-
this.currentRequest = undefined;
|
|
1005
|
-
this.emitter = new EventEmitter();
|
|
1006
|
-
this.debuggerLineRegex = /Brightscript\s+Debugger>\s*$/i;
|
|
1007
|
-
this.connect();
|
|
1008
|
-
}
|
|
1009
|
-
get isProcessing() {
|
|
1010
|
-
return this.currentRequest !== undefined;
|
|
1011
|
-
}
|
|
1012
|
-
get hasRequests() {
|
|
1013
|
-
return this.requests.length > 0;
|
|
1014
|
-
}
|
|
1015
|
-
on(eventName, handler) {
|
|
1016
|
-
this.emitter.on(eventName, handler);
|
|
1017
|
-
return () => {
|
|
1018
|
-
this.emitter.removeListener(eventName, handler);
|
|
1019
|
-
};
|
|
1020
|
-
}
|
|
1021
|
-
emit(eventName, data) {
|
|
1022
|
-
this.emitter.emit(eventName, data);
|
|
1023
|
-
}
|
|
1024
|
-
connect() {
|
|
1025
|
-
let allResponseText = '';
|
|
1026
|
-
let lastPartialLine = '';
|
|
1027
|
-
this.client.addListener('data', (data) => {
|
|
1028
|
-
let responseText = data.toString();
|
|
1029
|
-
const cumulative = lastPartialLine + responseText;
|
|
1030
|
-
if (!cumulative.endsWith('\n') && !this.checkForDebuggerPrompt(cumulative)) {
|
|
1031
|
-
// buffer was split and was not the result of a prompt, save the partial line
|
|
1032
|
-
lastPartialLine += responseText;
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
if (lastPartialLine) {
|
|
1036
|
-
// there was leftover lines, join the partial lines back together
|
|
1037
|
-
responseText = lastPartialLine + responseText;
|
|
1038
|
-
lastPartialLine = '';
|
|
1039
|
-
}
|
|
1040
|
-
//forward all raw console output
|
|
1041
|
-
this.emit('console-output', responseText);
|
|
1042
|
-
allResponseText += responseText;
|
|
1043
|
-
let foundDebuggerPrompt = this.checkForDebuggerPrompt(allResponseText);
|
|
1044
|
-
//if we are not processing, immediately broadcast the latest data
|
|
1045
|
-
if (!this.isProcessing) {
|
|
1046
|
-
this.emit('unhandled-console-output', allResponseText);
|
|
1047
|
-
allResponseText = '';
|
|
1048
|
-
if (foundDebuggerPrompt) {
|
|
1049
|
-
this.isAtDebuggerPrompt = true;
|
|
1050
|
-
if (this.hasRequests) {
|
|
1051
|
-
// There are requests waiting to be processed
|
|
1052
|
-
this.process();
|
|
1053
|
-
}
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
else {
|
|
1057
|
-
//if responseText produced a prompt, return the responseText
|
|
1058
|
-
if (foundDebuggerPrompt) {
|
|
1059
|
-
//resolve the command's promise (if it cares)
|
|
1060
|
-
this.isAtDebuggerPrompt = true;
|
|
1061
|
-
this.currentRequest.onComplete(allResponseText);
|
|
1062
|
-
allResponseText = '';
|
|
1063
|
-
this.currentRequest = undefined;
|
|
1064
|
-
//try to run the next request
|
|
1065
|
-
this.process();
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
/**
|
|
1071
|
-
* Checks the supplied string for the debugger input prompt
|
|
1072
|
-
* @param responseText
|
|
1073
|
-
*/
|
|
1074
|
-
checkForDebuggerPrompt(responseText) {
|
|
1075
|
-
let match = this.debuggerLineRegex.exec(responseText.trim());
|
|
1076
|
-
return (match);
|
|
1077
|
-
}
|
|
1078
|
-
/**
|
|
1079
|
-
* Schedule a command to be run. Resolves with the result once the command finishes
|
|
1080
|
-
* @param commandFunction
|
|
1081
|
-
* @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
|
|
1082
|
-
* @param forceExecute - if true, it is assumed the command can be run at any time and will be executed immediately
|
|
1083
|
-
* @param silent - if true, the command will be hidden from the output
|
|
1084
|
-
*/
|
|
1085
|
-
executeCommand(command, waitForPrompt, forceExecute = false, silent = false) {
|
|
1086
|
-
console.debug(`Execute command (and ${waitForPrompt ? 'do' : 'do not'} wait for prompt):`, command);
|
|
1087
|
-
return new Promise((resolve, reject) => {
|
|
1088
|
-
let executeCommand = () => {
|
|
1089
|
-
let commandText = `${command}\r\n`;
|
|
1090
|
-
if (!silent) {
|
|
1091
|
-
this.emit('console-output', command);
|
|
1092
|
-
}
|
|
1093
|
-
this.client.write(commandText);
|
|
1094
|
-
if (waitForPrompt) {
|
|
1095
|
-
// The act of executing this command means we are no longer at the debug prompt
|
|
1096
|
-
this.isAtDebuggerPrompt = false;
|
|
1097
|
-
}
|
|
1098
|
-
};
|
|
1099
|
-
let request = {
|
|
1100
|
-
executeCommand: executeCommand,
|
|
1101
|
-
onComplete: (data) => {
|
|
1102
|
-
console.debug(`Command finished (${waitForPrompt ? 'after waiting for prompt' : 'did not wait for prompt'}`, command);
|
|
1103
|
-
console.debug('Data:', data);
|
|
1104
|
-
resolve(data);
|
|
1105
|
-
},
|
|
1106
|
-
waitForPrompt: waitForPrompt
|
|
1107
|
-
};
|
|
1108
|
-
if (!waitForPrompt) {
|
|
1109
|
-
if (!this.isProcessing || forceExecute) {
|
|
1110
|
-
//fire and forget the command
|
|
1111
|
-
request.executeCommand();
|
|
1112
|
-
//the command doesn't care about the output, resolve it immediately
|
|
1113
|
-
request.onComplete(undefined);
|
|
1114
|
-
}
|
|
1115
|
-
else {
|
|
1116
|
-
// Skip this request as the device is not ready to accept the command or it can not be run at any time
|
|
1117
|
-
}
|
|
1118
|
-
}
|
|
1119
|
-
else {
|
|
1120
|
-
this.requests.push(request);
|
|
1121
|
-
if (this.isAtDebuggerPrompt) {
|
|
1122
|
-
//start processing since we are already at a debug prompt (safe to call multiple times)
|
|
1123
|
-
this.process();
|
|
1124
|
-
}
|
|
1125
|
-
else {
|
|
1126
|
-
// do not run the command until the device is at a debug prompt.
|
|
1127
|
-
// this will be detected in the data listener in the connect function
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
/**
|
|
1133
|
-
* Internal request processing function
|
|
1134
|
-
*/
|
|
1135
|
-
process() {
|
|
1136
|
-
if (this.isProcessing || !this.hasRequests) {
|
|
1137
|
-
return;
|
|
1138
|
-
}
|
|
1139
|
-
//get the oldest command
|
|
1140
|
-
let nextRequest = this.requests.shift();
|
|
1141
|
-
this.currentRequest = nextRequest;
|
|
1142
|
-
//run the request. the data listener will handle launching the next request once this one has finished processing
|
|
1143
|
-
nextRequest.executeCommand();
|
|
1144
|
-
}
|
|
1145
|
-
destroy() {
|
|
1146
|
-
this.client.removeAllListeners();
|
|
1147
|
-
this.client.destroy();
|
|
1148
|
-
this.client = undefined;
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1151
|
-
exports.RequestPipeline = RequestPipeline;
|
|
1152
1003
|
//# sourceMappingURL=TelnetAdapter.js.map
|