roku-debug 0.8.7 → 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.
Files changed (44) hide show
  1. package/CHANGELOG.md +14 -3
  2. package/dist/CompileErrorProcessor.d.ts +1 -0
  3. package/dist/CompileErrorProcessor.js +5 -4
  4. package/dist/CompileErrorProcessor.js.map +1 -1
  5. package/dist/LaunchConfiguration.d.ts +5 -0
  6. package/dist/SceneGraphDebugCommandController.d.ts +1 -0
  7. package/dist/SceneGraphDebugCommandController.js +11 -5
  8. package/dist/SceneGraphDebugCommandController.js.map +1 -1
  9. package/dist/adapters/DebugProtocolAdapter.d.ts +3 -7
  10. package/dist/adapters/DebugProtocolAdapter.js +16 -14
  11. package/dist/adapters/DebugProtocolAdapter.js.map +1 -1
  12. package/dist/adapters/TelnetAdapter.d.ts +7 -51
  13. package/dist/adapters/TelnetAdapter.js +209 -385
  14. package/dist/adapters/TelnetAdapter.js.map +1 -1
  15. package/dist/adapters/TelnetRequestPipeline.d.ts +51 -0
  16. package/dist/adapters/TelnetRequestPipeline.js +229 -0
  17. package/dist/adapters/TelnetRequestPipeline.js.map +1 -0
  18. package/dist/debugProtocol/Debugger.d.ts +1 -0
  19. package/dist/debugProtocol/Debugger.js +20 -19
  20. package/dist/debugProtocol/Debugger.js.map +1 -1
  21. package/dist/debugSession/BrightScriptDebugSession.d.ts +6 -0
  22. package/dist/debugSession/BrightScriptDebugSession.js +83 -36
  23. package/dist/debugSession/BrightScriptDebugSession.js.map +1 -1
  24. package/dist/interfaces.d.ts +11 -0
  25. package/dist/interfaces.js +16 -0
  26. package/dist/interfaces.js.map +1 -0
  27. package/dist/logging.d.ts +11 -0
  28. package/dist/logging.js +14 -0
  29. package/dist/logging.js.map +1 -0
  30. package/dist/managers/FileManager.d.ts +1 -0
  31. package/dist/managers/FileManager.js +3 -1
  32. package/dist/managers/FileManager.js.map +1 -1
  33. package/dist/managers/ProjectManager.d.ts +2 -0
  34. package/dist/managers/ProjectManager.js +5 -2
  35. package/dist/managers/ProjectManager.js.map +1 -1
  36. package/dist/managers/SourceMapManager.d.ts +1 -0
  37. package/dist/managers/SourceMapManager.js +5 -5
  38. package/dist/managers/SourceMapManager.js.map +1 -1
  39. package/dist/util.d.ts +19 -12
  40. package/dist/util.js +41 -40
  41. package/dist/util.js.map +1 -1
  42. package/package.json +4 -1
  43. package/roku-debug-0.9.0.tgz +0 -0
  44. package/roku-debug-0.8.7.tgz +0 -0
@@ -1,15 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RequestPipeline = exports.PrimativeType = exports.KeyType = exports.HighLevelType = exports.EventName = exports.TelnetAdapter = void 0;
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 net = require("net");
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,7 +75,7 @@ class TelnetAdapter {
71
75
  }, 0);
72
76
  }
73
77
  async activate() {
74
- util_1.util.logDebug('Activate TelnetAdapter');
78
+ this.logger.log('Activate TelnetAdapter');
75
79
  this.isActivated = true;
76
80
  await this.handleStartupIfReady();
77
81
  }
@@ -81,13 +85,13 @@ class TelnetAdapter {
81
85
  async handleStartupIfReady() {
82
86
  var _a;
83
87
  if (this.isActivated && this.isAppRunning) {
84
- util_1.util.logDebug('Handling startup');
88
+ this.logger.log('Handling startup');
85
89
  this.emit('start');
86
90
  //if we are already sitting at a debugger prompt, we need to emit the first suspend event.
87
91
  //If not, then there are probably still messages being received, so let the normal handler
88
92
  //emit the suspend event when it's ready
89
93
  if (this.isAtDebuggerPrompt === true) {
90
- util_1.util.logDebug('At debug prompt, so trigger suspend event');
94
+ this.logger.log(`At debug prompt, so trigger the 'suspend' event`);
91
95
  let threads = await this.getThreads();
92
96
  this.emit('suspend', (_a = threads[0]) === null || _a === void 0 ? void 0 : _a.threadId);
93
97
  }
@@ -101,21 +105,21 @@ class TelnetAdapter {
101
105
  */
102
106
  settle(client, name, maxWaitMilliseconds = 400) {
103
107
  const startTime = new Date();
104
- util_1.util.logDebug('Waiting for telnet client to settle');
108
+ this.logger.log('Waiting for telnet client to settle');
105
109
  return new Promise((resolve) => {
106
110
  let callCount = -1;
107
- function handler() {
111
+ const handler = () => {
108
112
  callCount++;
109
113
  let myCallCount = callCount;
110
114
  setTimeout(() => {
111
115
  //if no other calls have been made since the timeout started, then the listener has settled
112
116
  if (myCallCount === callCount) {
113
117
  client.removeListener(name, handler);
114
- util_1.util.logDebug(`Telnet client has settled after ${new Date().getTime() - startTime.getTime()} milliseconds`);
118
+ this.logger.log(`Telnet client has settled after ${new Date().getTime() - startTime.getTime()} milliseconds`);
115
119
  resolve(callCount);
116
120
  }
117
121
  }, maxWaitMilliseconds);
118
- }
122
+ };
119
123
  client.addListener(name, handler);
120
124
  //call the handler immediately so we have a timeout
121
125
  handler();
@@ -146,15 +150,15 @@ class TelnetAdapter {
146
150
  * Connect to the telnet session. This should be called before the channel is launched.
147
151
  */
148
152
  async connect() {
149
- util_1.util.logDebug('Establishing telnet connection');
153
+ this.logger.log('Establishing telnet connection');
150
154
  let deferred = (0, util_1.defer)();
151
155
  this.isInMicroDebugger = false;
152
156
  this.isNextBreakpointSkipped = false;
153
157
  try {
154
- util_1.util.logDebug('Pressing home button');
158
+ this.logger.log('Pressing home button');
155
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
156
160
  await rokuDeploy.pressHomeButton(this.host);
157
- let client = new net.Socket();
161
+ let client = new net_1.Socket();
158
162
  //listen for the close event
159
163
  client.addListener('close', (err, data) => {
160
164
  this.emit('close');
@@ -165,13 +169,14 @@ class TelnetAdapter {
165
169
  });
166
170
  const settlePromise = this.settle(client, 'data');
167
171
  client.connect(8085, this.host, () => {
168
- util_1.util.logDebug(`Telnet connection established to ${this.host}`);
172
+ this.logger.log(`Telnet connection established to ${this.host}`);
169
173
  this.connected = true;
170
174
  this.emit('connected', this.connected);
171
175
  });
172
176
  await settlePromise;
173
177
  //hook up the pipeline to the socket
174
- this.requestPipeline = new RequestPipeline(client);
178
+ this.requestPipeline = new TelnetRequestPipeline_1.TelnetRequestPipeline(client);
179
+ this.requestPipeline.connect();
175
180
  //forward all raw console output
176
181
  this.requestPipeline.on('console-output', (output) => {
177
182
  this.processBreakpoints(output);
@@ -197,14 +202,14 @@ class TelnetAdapter {
197
202
  }
198
203
  // short circuit after the output has been sent as console output
199
204
  if (hasRuntimeError) {
200
- util_1.util.logDebugFenced('Detected runtime error in output', responseText);
205
+ this.logger.log('Detected runtime error in output', { responseText });
201
206
  this.isAtDebuggerPrompt = true;
202
207
  return;
203
208
  }
204
209
  this.compileErrorProcessor.processUnhandledLines(responseText);
205
210
  let match;
206
211
  if (this.isAtCannotContinue(responseText)) {
207
- util_1.util.logDebug('is at cannot continue');
212
+ this.logger.log('is at cannot continue');
208
213
  this.isAtDebuggerPrompt = true;
209
214
  return;
210
215
  }
@@ -213,7 +218,7 @@ class TelnetAdapter {
213
218
  // eslint-disable-next-line no-cond-assign
214
219
  if (match = /\[scrpt.ctx.run.enter\]/i.exec(responseText.trim())) {
215
220
  this.isAppRunning = true;
216
- util_1.util.logDebug('Running beacon detected');
221
+ this.logger.log('Running beacon detected', { responseText });
217
222
  void this.handleStartupIfReady();
218
223
  }
219
224
  //watch for the end of the program
@@ -222,28 +227,29 @@ class TelnetAdapter {
222
227
  this.beginAppExit();
223
228
  }
224
229
  //watch for debugger prompt output
225
- if (util_1.util.checkForDebuggerPrompt(responseText)) {
226
- util_1.util.logDebugFenced('Debugger prompt detected in', responseText);
230
+ if (util_1.util.endsWithDebuggerPrompt(responseText)) {
231
+ this.logger.log('Debugger prompt detected in', { responseText });
227
232
  //if we are activated AND this is the first time seeing the debugger prompt since a continue/step action
228
233
  if (this.isNextBreakpointSkipped) {
229
- util_1.util.logDebug('This debugger is flagged to be skipped');
234
+ this.logger.log('This debugger is flagged to be skipped');
230
235
  this.isInMicroDebugger = false;
231
236
  this.isNextBreakpointSkipped = false;
232
- void this.requestPipeline.executeCommand('c', false, false, false);
237
+ void this.requestPipeline.executeCommand('c', { waitForPrompt: false, insertAtFront: true });
233
238
  }
234
239
  else {
235
240
  if (this.isActivated && this.isAtDebuggerPrompt === false) {
236
241
  this.isAtDebuggerPrompt = true;
237
- util_1.util.logDebug('Sending the "suspend" event to the client');
242
+ this.logger.log('Sending the "suspend" event to the client');
238
243
  this.emit('suspend');
239
244
  }
240
245
  else {
241
- util_1.util.logDebug('Skipping "suspend" event because we are already suspended');
246
+ this.logger.log('Skipping "suspend" event because we are already suspended');
242
247
  this.isAtDebuggerPrompt = true;
243
248
  }
244
249
  }
245
250
  }
246
251
  else {
252
+ this.logger.debug('responseText does not end with debugger prompt. isAtDebuggerPrompt = false', { responseText });
247
253
  this.isAtDebuggerPrompt = false;
248
254
  }
249
255
  }
@@ -257,9 +263,9 @@ class TelnetAdapter {
257
263
  return deferred.promise;
258
264
  }
259
265
  beginAppExit() {
260
- util_1.util.logDebug('Beginning app exit');
266
+ this.logger.log('Beginning app exit');
261
267
  this.compileErrorProcessor.compileErrorTimer = setTimeout(() => {
262
- console.log('emitting app-exit');
268
+ this.logger.info('emitting app-exit');
263
269
  this.isAppRunning = false;
264
270
  this.emit('app-exit');
265
271
  }, 200);
@@ -304,42 +310,42 @@ class TelnetAdapter {
304
310
  * Send command to step over
305
311
  */
306
312
  stepOver() {
307
- util_1.util.logDebug('stepOver');
313
+ this.logger.log('stepOver');
308
314
  this.clearCache();
309
- return this.requestPipeline.executeCommand('over', false);
315
+ return this.requestPipeline.executeCommand('over', { waitForPrompt: false, insertAtFront: true });
310
316
  }
311
317
  stepInto() {
312
- util_1.util.logDebug('stepInto');
318
+ this.logger.log('stepInto');
313
319
  this.clearCache();
314
- return this.requestPipeline.executeCommand('step', false);
320
+ return this.requestPipeline.executeCommand('step', { waitForPrompt: false, insertAtFront: true });
315
321
  }
316
322
  stepOut() {
317
- util_1.util.logDebug('stepOut');
323
+ this.logger.log('stepOut');
318
324
  this.clearCache();
319
- return this.requestPipeline.executeCommand('out', false);
325
+ return this.requestPipeline.executeCommand('out', { waitForPrompt: false, insertAtFront: true });
320
326
  }
321
327
  /**
322
328
  * Tell the brightscript program to continue (i.e. resume program)
323
329
  */
324
330
  continue() {
325
- util_1.util.logDebug('continue');
331
+ this.logger.log('continue');
326
332
  this.clearCache();
327
- return this.requestPipeline.executeCommand('c', false);
333
+ return this.requestPipeline.executeCommand('c', { waitForPrompt: false, insertAtFront: true });
328
334
  }
329
335
  /**
330
336
  * Tell the brightscript program to pause (fall into debug mode)
331
337
  */
332
338
  pause() {
333
- util_1.util.logDebug('pause');
339
+ this.logger.log('pause');
334
340
  this.clearCache();
335
- //send the kill signal, which breaks into debugger mode
336
- return this.requestPipeline.executeCommand('\x03;', false, true);
341
+ //send the kill signal, which breaks into debugger mode. This gets written immediately, regardless of debugger prompt status.
342
+ this.requestPipeline.write('\x03;');
337
343
  }
338
344
  /**
339
345
  * Clears the state, which means that everything will be retrieved fresh next time it is requested
340
346
  */
341
347
  clearCache() {
342
- util_1.util.logDebug('Clearing TelnetAdapter cache');
348
+ this.logger.info('Clearing TelnetAdapter cache');
343
349
  this.cache = {};
344
350
  this.isAtDebuggerPrompt = false;
345
351
  }
@@ -348,26 +354,26 @@ class TelnetAdapter {
348
354
  * @param command the command to execute. If the command does not start with `print` the command will be prefixed with `print ` because
349
355
  */
350
356
  async evaluate(command) {
351
- util_1.util.logDebug('evaluate ', { command });
357
+ this.logger.log('evaluate ', { command });
352
358
  if (!this.isAtDebuggerPrompt) {
353
359
  throw new Error('Cannot run evaluate: debugger is not paused');
354
360
  }
355
361
  //clear the cache (we don't know what command the user entered)
356
362
  this.clearCache();
357
363
  //don't wait for the output...we don't know what command the user entered
358
- let responseText = await this.requestPipeline.executeCommand(command, true);
364
+ let responseText = await this.requestPipeline.executeCommand(command, { waitForPrompt: true });
359
365
  //we know that if we got a response, we are back at a debugger prompt
360
366
  this.isAtDebuggerPrompt = true;
361
367
  return responseText;
362
368
  }
363
369
  async getStackTrace() {
364
- util_1.util.logDebug('Get stack trace');
370
+ this.logger.log(TelnetAdapter.prototype.getStackTrace.name);
365
371
  if (!this.isAtDebuggerPrompt) {
366
372
  throw new Error('Cannot get stack trace: debugger is not paused');
367
373
  }
368
374
  return this.resolve('stackTrace', async () => {
369
375
  //perform a request to load the stack trace
370
- let responseText = await this.requestPipeline.executeCommand('bt', true);
376
+ let responseText = (await this.requestPipeline.executeCommand('bt', { waitForPrompt: true })).trim();
371
377
  let regexp = /#(\d+)\s+(?:function|sub)\s+([\$\w\d]+).*\s+file\/line:\s+(.*)\((\d+)\)/ig;
372
378
  let matches;
373
379
  let frames = [];
@@ -394,16 +400,6 @@ class TelnetAdapter {
394
400
  return frames;
395
401
  });
396
402
  }
397
- /**
398
- * Runs a regex to get the content between telnet commands
399
- * @param value
400
- */
401
- getExpressionDetails(value) {
402
- const match = /(.*?)\r?\nBrightscript Debugger>\s*/is.exec(value);
403
- if (match) {
404
- return match[1];
405
- }
406
- }
407
403
  /**
408
404
  * Runs a regex to check if the target is an object and get the type if it is
409
405
  * @param value
@@ -429,15 +425,15 @@ class TelnetAdapter {
429
425
  * @param scope
430
426
  */
431
427
  async getScopeVariables(scope) {
432
- util_1.util.logDebug('getScopeVariables', { scope });
428
+ this.logger.log('getScopeVariables', { scope });
433
429
  if (!this.isAtDebuggerPrompt) {
434
430
  throw new Error('Cannot resolve variable: debugger is not paused');
435
431
  }
436
432
  return this.resolve(`Scope Variables`, async () => {
437
433
  let data;
438
434
  let vars = [];
439
- data = await this.requestPipeline.executeCommand(`var`, true);
440
- let splitData = data.split('\n');
435
+ data = await this.requestPipeline.executeCommand(`var`, { waitForPrompt: true });
436
+ let splitData = data.trim().split('\n');
441
437
  for (const line of splitData) {
442
438
  let match;
443
439
  if (!line.includes('Brightscript Debugger') && (match = this.getFirstWord(line))) {
@@ -456,123 +452,141 @@ class TelnetAdapter {
456
452
  * @param expression
457
453
  */
458
454
  async getVariable(expression) {
459
- util_1.util.logDebug('getVariable', { expression });
455
+ const logger = this.logger.createLogger('[getVariable]');
456
+ logger.info('begin', { expression });
460
457
  if (!this.isAtDebuggerPrompt) {
461
458
  throw new Error('Cannot resolve variable: debugger is not paused');
462
459
  }
463
- return this.resolve(`variable: ${expression}`, async () => {
464
- let expressionType = await this.getVariableType(expression);
465
- let lowerExpressionType = expressionType ? expressionType.toLowerCase() : null;
466
- let data;
467
- //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
468
- if (lowerExpressionType === 'string' || lowerExpressionType === 'rostring') {
469
- data = await this.requestPipeline.executeCommand(`print "--string-wrap--" + ${expression} + "--string-wrap--"`, true);
470
- //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
471
- }
472
- else if (['roarray', 'rolist', 'roxmllist', 'robytearray'].includes(lowerExpressionType)) {
473
- const command = [
474
- `for each vscodeLoopItem in ${expression} : print ` +
475
- ` "vscode_type_start:" + type(vscodeLoopItem) + ":vscode_type_stop "`,
476
- ` "vscode_is_string:"; (invalid <> GetInterface(vscodeLoopItem, "ifString"))`,
477
- ` vscodeLoopItem :` +
478
- ` end for`
479
- ].join(';');
480
- data = await this.requestPipeline.executeCommand(command, true);
481
- }
482
- else if (['roassociativearray', 'rosgnode'].includes(lowerExpressionType)) {
483
- const command = [
484
- `for each vscodeLoopKey in ${expression}.keys(): print` +
485
- ` "vscode_key_start:" + vscodeLoopKey + ":vscode_key_stop "`,
486
- ` "vscode_type_start:" + type(${expression}[vscodeLoopKey]) + ":vscode_type_stop "`,
487
- ` "vscode_is_string:"; (invalid <> GetInterface(${expression}[vscodeLoopKey], "ifString"))`,
488
- ` ${expression}[vscodeLoopKey] :` +
489
- ' end for'
490
- ].join(';');
491
- data = await this.requestPipeline.executeCommand(command, true);
492
- }
493
- else {
494
- data = await this.requestPipeline.executeCommand(`print ${expression}`, true);
495
- }
496
- let match = this.getExpressionDetails(data);
497
- if (match !== undefined) {
498
- let value = match;
499
- if (lowerExpressionType === 'string' || lowerExpressionType === 'rostring') {
500
- value = value.trim().replace(/--string-wrap--/g, '');
501
- //add an escape character in front of any existing quotes
502
- value = value.replace(/"/g, '\\"');
503
- //wrap the string value with literal quote marks
504
- value = '"' + value + '"';
505
- }
506
- let highLevelType = this.getHighLevelType(expressionType);
507
- let children;
508
- if (highLevelType === HighLevelType.array || ['roassociativearray', 'rosgnode', 'roxmllist', 'robytearray'].includes(lowerExpressionType)) {
509
- //the print statment will always have 1 trailing newline, so remove that.
510
- value = util_1.util.removeTrailingNewline(value);
511
- //the array/associative array print is a loop of every value, so handle that
512
- children = this.getForLoopPrintedChildren(expression, value);
513
- }
514
- else if (highLevelType === HighLevelType.object) {
515
- children = this.getObjectChildren(expression, value.trim());
516
- }
517
- if (['rostring', 'roint', 'rointeger', 'rolonginteger', 'rofloat', 'rodouble', 'roboolean', 'rointrinsicdouble'].includes(lowerExpressionType)) {
518
- return {
519
- name: expression,
520
- value: util_1.util.removeTrailingNewline(value),
521
- type: expressionType,
522
- highLevelType: HighLevelType.primative,
523
- evaluateName: expression,
524
- children: []
525
- };
526
- }
527
- //add a computed `[[children]]` property to allow expansion of node children
528
- if (lowerExpressionType === 'rosgnode') {
529
- let nodeChildren = {
530
- name: '[[children]]',
531
- type: 'roArray',
532
- highLevelType: 'array',
533
- evaluateName: `${expression}.getChildren(-1, 0)`,
534
- children: []
535
- };
536
- children.push(nodeChildren);
537
- }
538
- //xml elements won't display on their own, so we need to create some sub elements
539
- if (lowerExpressionType === 'roxmlelement') {
540
- //add a computed `[[children]]` property to allow expansion of node children
541
- children.push({
542
- name: '[[children]]',
543
- type: 'roArray',
544
- highLevelType: HighLevelType.array,
545
- evaluateName: `${expression}.GetChildNodes()`,
546
- children: []
547
- });
548
- children.push({
549
- name: '[[attributes]]',
550
- type: 'roArray',
551
- highLevelType: HighLevelType.array,
552
- evaluateName: `${expression}.GetAttributes()`,
553
- children: []
554
- });
555
- //look up the element name right now
556
- const container = await this.getVariable(`${expression}.GetName()`);
557
- container.name = '[[name]]';
558
- children.push(container);
559
- }
560
- //if this item is an array or a list, add the item count to the end of the type
561
- if (highLevelType === HighLevelType.array) {
562
- //TODO re-enable once we find how to refresh watch/variables panel, since lazy loaded arrays can't show a length
563
- //expressionType += `(${children.length})`;
564
- }
565
- let container = {
566
- name: expression,
567
- evaluateName: expression,
568
- type: expressionType,
569
- value: value.trim(),
570
- highLevelType: highLevelType,
571
- children: children
572
- };
573
- return container;
574
- }
575
- });
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;
576
590
  }
577
591
  /**
578
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.
@@ -647,22 +661,22 @@ class TelnetAdapter {
647
661
  let collectionEnd;
648
662
  if (line.includes('<Component: roList>')) {
649
663
  collectionEnd = ')';
650
- child.highLevelType = HighLevelType.array;
664
+ child.highLevelType = interfaces_1.HighLevelType.array;
651
665
  child.type = objectType;
652
666
  }
653
667
  else if (line.includes('<Component: roArray>')) {
654
668
  collectionEnd = ']';
655
- child.highLevelType = HighLevelType.array;
669
+ child.highLevelType = interfaces_1.HighLevelType.array;
656
670
  child.type = this.getObjectType(line);
657
671
  }
658
672
  else if (line.includes('<Component: roByteArray>')) {
659
673
  collectionEnd = ']';
660
- child.highLevelType = HighLevelType.array;
674
+ child.highLevelType = interfaces_1.HighLevelType.array;
661
675
  child.type = this.getObjectType(line);
662
676
  }
663
677
  else if (line.includes('<Component: roAssociativeArray>') || isRoSGNode) {
664
678
  collectionEnd = '}';
665
- child.highLevelType = HighLevelType.object;
679
+ child.highLevelType = interfaces_1.HighLevelType.object;
666
680
  child.type = this.getObjectType(line);
667
681
  }
668
682
  let collectionLineList = [line];
@@ -676,34 +690,24 @@ class TelnetAdapter {
676
690
  }
677
691
  //we have reached the end of the collection. scrap children because they need evaluated in a separate call to compute their types
678
692
  child.children = [];
679
- if (isRoSGNode) {
680
- let nodeChildrenProperty = {
681
- name: '[[children]]',
682
- type: 'roArray',
683
- highLevelType: 'array',
684
- evaluateName: `${child.evaluateName}.getChildren(-1,0)`,
685
- children: []
686
- };
687
- child.children.push(nodeChildrenProperty);
688
- }
689
693
  //this if block must pre-seek the `line.indexOf('<Component') > -1` line because roInvalid is a component too.
690
694
  }
691
695
  else if (objectType === 'roInvalid') {
692
- child.highLevelType = HighLevelType.uninitialized;
696
+ child.highLevelType = interfaces_1.HighLevelType.uninitialized;
693
697
  child.type = 'roInvalid';
694
698
  child.value = 'roInvalid';
695
699
  child.children = undefined;
696
700
  }
697
701
  else if (line.includes('<Component:')) {
698
702
  //handle things like nodes
699
- child.highLevelType = HighLevelType.object;
703
+ child.highLevelType = interfaces_1.HighLevelType.object;
700
704
  child.type = objectType;
701
705
  }
702
706
  else {
703
707
  //is some primative type
704
708
  child.type = type;
705
709
  child.value = line.trim();
706
- child.highLevelType = HighLevelType.primative;
710
+ child.highLevelType = interfaces_1.HighLevelType.primative;
707
711
  child.children = undefined;
708
712
  }
709
713
  children.push(child);
@@ -749,7 +753,7 @@ class TelnetAdapter {
749
753
  else {
750
754
  child.type = this.getPrimativeTypeFromValue(line);
751
755
  child.value = line;
752
- child.highLevelType = HighLevelType.primative;
756
+ child.highLevelType = interfaces_1.HighLevelType.primative;
753
757
  }
754
758
  children.push(child);
755
759
  arrayIndex++;
@@ -794,7 +798,7 @@ class TelnetAdapter {
794
798
  child = {
795
799
  name: line,
796
800
  type: '<ERROR>',
797
- highLevelType: HighLevelType.uninitialized,
801
+ highLevelType: interfaces_1.HighLevelType.uninitialized,
798
802
  evaluateName: undefined,
799
803
  variablePath: [],
800
804
  elementCount: -1,
@@ -819,7 +823,7 @@ class TelnetAdapter {
819
823
  else {
820
824
  child.type = this.getPrimativeTypeFromValue(trimmedLine);
821
825
  child.value = lineParseResult.value;
822
- child.highLevelType = HighLevelType.primative;
826
+ child.highLevelType = interfaces_1.HighLevelType.primative;
823
827
  }
824
828
  }
825
829
  children.push(child);
@@ -836,24 +840,24 @@ class TelnetAdapter {
836
840
  */
837
841
  getHighLevelType(expressionType) {
838
842
  if (!expressionType) {
839
- throw new Error(`Unknown expression type: ${expressionType}`);
843
+ return interfaces_1.HighLevelType.unknown;
840
844
  }
841
845
  expressionType = expressionType.toLowerCase();
842
846
  let primativeTypes = ['boolean', 'integer', 'longinteger', 'float', 'double', 'string', 'rostring', 'invalid'];
843
847
  if (primativeTypes.includes(expressionType)) {
844
- return HighLevelType.primative;
848
+ return interfaces_1.HighLevelType.primative;
845
849
  }
846
850
  else if (expressionType === 'roarray' || expressionType === 'rolist') {
847
- return HighLevelType.array;
851
+ return interfaces_1.HighLevelType.array;
848
852
  }
849
853
  else if (expressionType === 'function') {
850
- return HighLevelType.function;
854
+ return interfaces_1.HighLevelType.function;
851
855
  }
852
856
  else if (expressionType === '<uninitialized>') {
853
- return HighLevelType.uninitialized;
857
+ return interfaces_1.HighLevelType.uninitialized;
854
858
  }
855
859
  else {
856
- return HighLevelType.object;
860
+ return interfaces_1.HighLevelType.object;
857
861
  }
858
862
  }
859
863
  /**
@@ -866,17 +870,10 @@ class TelnetAdapter {
866
870
  }
867
871
  expression = `Type(${expression})`;
868
872
  return this.resolve(`${expression}`, async () => {
869
- let data = await this.requestPipeline.executeCommand(`print ${expression}`, true);
870
- let match = this.getExpressionDetails(data);
871
- if (match) {
872
- let typeValue = match;
873
- //remove whitespace
874
- typeValue = typeValue.trim();
875
- return typeValue;
876
- }
877
- else {
878
- return null;
879
- }
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;
880
877
  });
881
878
  }
882
879
  /**
@@ -887,11 +884,11 @@ class TelnetAdapter {
887
884
  resolve(key, factory) {
888
885
  try {
889
886
  if (this.cache[key]) {
890
- util_1.util.logDebug(`resolve cache "${key}": already exists`);
887
+ this.logger.debug(`resolve cache "${key}": already exists`);
891
888
  return this.cache[key];
892
889
  }
893
890
  else {
894
- util_1.util.logDebug(`resolve cache "${key}": calling factory`);
891
+ this.logger.debug(`resolve cache "${key}": calling factory`);
895
892
  const result = factory();
896
893
  this.cache[key] = Promise.resolve(result);
897
894
  return this.cache[key];
@@ -905,14 +902,14 @@ class TelnetAdapter {
905
902
  * Get a list of threads. The first thread in the list is the active thread
906
903
  */
907
904
  async getThreads() {
908
- util_1.util.logDebug('getThreads');
905
+ this.logger.log('getThreads');
909
906
  if (!this.isAtDebuggerPrompt) {
910
- util_1.util.logDebug('Cannot get threads: debugger is not paused');
907
+ this.logger.log('Cannot get threads: debugger is not paused');
911
908
  return [];
912
909
  }
913
910
  return this.resolve('threads', async () => {
914
- let data = await this.requestPipeline.executeCommand('threads', true);
915
- let dataString = data.toString();
911
+ let data = await this.requestPipeline.executeCommand('threads', { waitForPrompt: true });
912
+ let dataString = data.toString().trim();
916
913
  let matches = /^\s+(\d+\*)\s+(.*)\((\d+)\)\s+(.*)/gm.exec(dataString);
917
914
  let threads = [];
918
915
  if (matches) {
@@ -956,20 +953,6 @@ class TelnetAdapter {
956
953
  //needs to be async to match the DebugProtocolAdapter implementation
957
954
  return Promise.resolve();
958
955
  }
959
- /**
960
- * Make sure any active Brightscript Debugger threads are exited
961
- */
962
- async exitActiveBrightscriptDebugger() {
963
- if (this.requestPipeline) {
964
- let commandsExecuted = 0;
965
- do {
966
- let data = await this.requestPipeline.executeCommand(`exit`, false);
967
- // This seems to work without the delay but I wonder about slower devices
968
- // await setTimeout[Object.getOwnPropertySymbols(setTimeout)[0]](100);
969
- commandsExecuted++;
970
- } while (commandsExecuted < 10);
971
- }
972
- }
973
956
  // #region Rendezvous Tracker pass though functions
974
957
  /**
975
958
  * Passes the debug functions used to locate the client files and lines to the RendezvousTracker
@@ -1003,14 +986,6 @@ var EventName;
1003
986
  (function (EventName) {
1004
987
  EventName["suspend"] = "suspend";
1005
988
  })(EventName = exports.EventName || (exports.EventName = {}));
1006
- var HighLevelType;
1007
- (function (HighLevelType) {
1008
- HighLevelType["primative"] = "primative";
1009
- HighLevelType["array"] = "array";
1010
- HighLevelType["function"] = "function";
1011
- HighLevelType["object"] = "object";
1012
- HighLevelType["uninitialized"] = "uninitialized";
1013
- })(HighLevelType = exports.HighLevelType || (exports.HighLevelType = {}));
1014
989
  var KeyType;
1015
990
  (function (KeyType) {
1016
991
  KeyType["string"] = "String";
@@ -1025,155 +1000,4 @@ var PrimativeType;
1025
1000
  PrimativeType["integer"] = "Integer";
1026
1001
  PrimativeType["float"] = "Float";
1027
1002
  })(PrimativeType = exports.PrimativeType || (exports.PrimativeType = {}));
1028
- class RequestPipeline {
1029
- constructor(client) {
1030
- this.client = client;
1031
- this.requests = [];
1032
- this.isAtDebuggerPrompt = false;
1033
- this.currentRequest = undefined;
1034
- this.emitter = new EventEmitter();
1035
- /**
1036
- * Used to help with logging
1037
- */
1038
- this.commandIdSequence = 0;
1039
- this.connect();
1040
- }
1041
- get isProcessing() {
1042
- return this.currentRequest !== undefined;
1043
- }
1044
- get hasRequests() {
1045
- return this.requests.length > 0;
1046
- }
1047
- on(eventName, handler) {
1048
- this.emitter.on(eventName, handler);
1049
- return () => {
1050
- this.emitter.removeListener(eventName, handler);
1051
- };
1052
- }
1053
- emit(eventName, data) {
1054
- this.emitter.emit(eventName, data);
1055
- }
1056
- connect() {
1057
- let allResponseText = '';
1058
- let lastPartialLine = '';
1059
- this.client.addListener('data', (data) => {
1060
- let responseText = data.toString();
1061
- util_1.util.logDebugFenced('Raw telnet data', responseText);
1062
- const cumulative = lastPartialLine + responseText;
1063
- //ensure all debugger prompts appear completely on their own line
1064
- responseText = util_1.util.ensureDebugPromptOnOwnLine(responseText);
1065
- if (!cumulative.endsWith('\n') && !util_1.util.checkForDebuggerPrompt(cumulative)) {
1066
- // buffer was split and was not the result of a prompt, save the partial line
1067
- lastPartialLine += responseText;
1068
- return;
1069
- }
1070
- if (lastPartialLine) {
1071
- // there was leftover lines, join the partial lines back together
1072
- responseText = lastPartialLine + responseText;
1073
- lastPartialLine = '';
1074
- }
1075
- //forward all raw console output
1076
- this.emit('console-output', responseText);
1077
- allResponseText += responseText;
1078
- let foundDebuggerPrompt = util_1.util.checkForDebuggerPrompt(allResponseText);
1079
- //if we are not processing, immediately broadcast the latest data
1080
- if (!this.isProcessing) {
1081
- this.emit('unhandled-console-output', allResponseText);
1082
- allResponseText = '';
1083
- if (foundDebuggerPrompt) {
1084
- this.isAtDebuggerPrompt = true;
1085
- if (this.hasRequests) {
1086
- // There are requests waiting to be processed
1087
- this.process();
1088
- }
1089
- }
1090
- }
1091
- else {
1092
- //if responseText produced a prompt, return the responseText
1093
- if (foundDebuggerPrompt) {
1094
- //resolve the command's promise (if it cares)
1095
- this.isAtDebuggerPrompt = true;
1096
- this.currentRequest.onComplete(allResponseText);
1097
- allResponseText = '';
1098
- this.currentRequest = undefined;
1099
- //try to run the next request
1100
- this.process();
1101
- }
1102
- }
1103
- });
1104
- }
1105
- /**
1106
- * Schedule a command to be run. Resolves with the result once the command finishes
1107
- * @param commandFunction
1108
- * @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
1109
- * @param forceExecute - if true, it is assumed the command can be run at any time and will be executed immediately
1110
- * @param silent - if true, the command will be hidden from the output
1111
- */
1112
- executeCommand(command, waitForPrompt, forceExecute = false, silent = false) {
1113
- const commandId = this.commandIdSequence++;
1114
- util_1.util.logDebug(`Command ${commandId} execute ${JSON.stringify(command)} and ${waitForPrompt ? '' : ' don\'t'} wait for prompt): \n`, command, '\n');
1115
- return new Promise((resolve, reject) => {
1116
- let executeCommand = () => {
1117
- let commandText = `${command}\r\n`;
1118
- if (!silent) {
1119
- this.emit('console-output', command);
1120
- }
1121
- this.client.write(commandText);
1122
- if (waitForPrompt) {
1123
- // The act of executing this command means we are no longer at the debug prompt
1124
- this.isAtDebuggerPrompt = false;
1125
- }
1126
- };
1127
- let request = {
1128
- executeCommand: executeCommand,
1129
- onComplete: (data) => {
1130
- util_1.util.logDebugFenced(`Command ${commandId} execute ${JSON.stringify(command)} result`, data);
1131
- resolve(data);
1132
- },
1133
- waitForPrompt: waitForPrompt
1134
- };
1135
- if (!waitForPrompt) {
1136
- if (!this.isProcessing || forceExecute) {
1137
- //fire and forget the command
1138
- request.executeCommand();
1139
- //the command doesn't care about the output, resolve it immediately
1140
- request.onComplete(undefined);
1141
- }
1142
- else {
1143
- // Skip this request as the device is not ready to accept the command or it can not be run at any time
1144
- }
1145
- }
1146
- else {
1147
- this.requests.push(request);
1148
- if (this.isAtDebuggerPrompt) {
1149
- //start processing since we are already at a debug prompt (safe to call multiple times)
1150
- this.process();
1151
- }
1152
- else {
1153
- // do not run the command until the device is at a debug prompt.
1154
- // this will be detected in the data listener in the connect function
1155
- }
1156
- }
1157
- });
1158
- }
1159
- /**
1160
- * Internal request processing function
1161
- */
1162
- process() {
1163
- if (this.isProcessing || !this.hasRequests) {
1164
- return;
1165
- }
1166
- //get the oldest command
1167
- let nextRequest = this.requests.shift();
1168
- this.currentRequest = nextRequest;
1169
- //run the request. the data listener will handle launching the next request once this one has finished processing
1170
- nextRequest.executeCommand();
1171
- }
1172
- destroy() {
1173
- this.client.removeAllListeners();
1174
- this.client.destroy();
1175
- this.client = undefined;
1176
- }
1177
- }
1178
- exports.RequestPipeline = RequestPipeline;
1179
1003
  //# sourceMappingURL=TelnetAdapter.js.map