| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library debugger_page_element; | 5 library debugger_page_element; |
| 6 | 6 |
| 7 import 'dart:async'; |
| 7 import 'dart:html'; | 8 import 'dart:html'; |
| 8 import 'observatory_element.dart'; | 9 import 'observatory_element.dart'; |
| 10 import 'package:observatory/cli.dart'; |
| 9 import 'package:observatory/service.dart'; | 11 import 'package:observatory/service.dart'; |
| 10 import 'package:polymer/polymer.dart'; | 12 import 'package:polymer/polymer.dart'; |
| 11 | 13 |
| 14 // TODO(turnidge): Move Debugger, DebuggerCommand to their own lib. |
| 15 abstract class DebuggerCommand extends Command { |
| 16 Debugger debugger; |
| 17 |
| 18 DebuggerCommand(this.debugger, name, children) |
| 19 : super(name, children); |
| 20 } |
| 21 |
| 22 class HelpCommand extends DebuggerCommand { |
| 23 HelpCommand(Debugger debugger) : super(debugger, 'help', []); |
| 24 |
| 25 Future run(List<String> args) { |
| 26 var con = debugger.console; |
| 27 con.printLine('List of commands:'); |
| 28 con.newline(); |
| 29 |
| 30 // TODO(turnidge): Build a real help system. |
| 31 List completions = debugger.cmd.completeCommand(''); |
| 32 completions = completions.map((s )=> s.trimRight()).toList(); |
| 33 completions.sort(); |
| 34 con.printLine(completions.toString()); |
| 35 con.newline(); |
| 36 con.printLine("Command prefixes are accepted (e.g. 'h' for 'help')"); |
| 37 con.printLine("Hit [TAB] to complete a command (try 'i[TAB][TAB]')"); |
| 38 con.printLine("Hit [ENTER] to repeat the last command"); |
| 39 |
| 40 return new Future.value(null); |
| 41 } |
| 42 } |
| 43 |
| 44 class PauseCommand extends DebuggerCommand { |
| 45 PauseCommand(Debugger debugger) : super(debugger, 'pause', []); |
| 46 |
| 47 Future run(List<String> args) { |
| 48 if (!debugger.isolatePaused()) { |
| 49 return debugger.isolate.pause(); |
| 50 } else { |
| 51 debugger.console.printLine('The program is already paused'); |
| 52 return new Future.value(null); |
| 53 } |
| 54 } |
| 55 } |
| 56 |
| 57 class ContinueCommand extends DebuggerCommand { |
| 58 ContinueCommand(Debugger debugger) : super(debugger, 'continue', []); |
| 59 |
| 60 Future run(List<String> args) { |
| 61 if (debugger.isolatePaused()) { |
| 62 return debugger.isolate.resume().then((_) { |
| 63 debugger.warnOutOfDate(); |
| 64 }); |
| 65 } else { |
| 66 debugger.console.printLine('The program must be paused'); |
| 67 return new Future.value(null); |
| 68 } |
| 69 } |
| 70 } |
| 71 |
| 72 class NextCommand extends DebuggerCommand { |
| 73 NextCommand(Debugger debugger) : super(debugger, 'next', []); |
| 74 |
| 75 Future run(List<String> args) { |
| 76 if (debugger.isolatePaused()) { |
| 77 var event = debugger.isolate.pauseEvent; |
| 78 if (event.eventType == 'IsolateCreated') { |
| 79 debugger.console.printLine("Type 'continue' to start the isolate"); |
| 80 return new Future.value(null); |
| 81 } |
| 82 if (event.eventType == 'IsolateShutdown') { |
| 83 debugger.console.printLine("Type 'continue' to exit the isolate"); |
| 84 return new Future.value(null); |
| 85 } |
| 86 return debugger.isolate.stepOver(); |
| 87 } else { |
| 88 debugger.console.printLine('The program is already running'); |
| 89 return new Future.value(null); |
| 90 } |
| 91 } |
| 92 } |
| 93 |
| 94 class StepCommand extends DebuggerCommand { |
| 95 StepCommand(Debugger debugger) : super(debugger, 'step', []); |
| 96 |
| 97 Future run(List<String> args) { |
| 98 if (debugger.isolatePaused()) { |
| 99 var event = debugger.isolate.pauseEvent; |
| 100 if (event.eventType == 'IsolateCreated') { |
| 101 debugger.console.printLine("Type 'continue' to start the isolate"); |
| 102 return new Future.value(null); |
| 103 } |
| 104 if (event.eventType == 'IsolateShutdown') { |
| 105 debugger.console.printLine("Type 'continue' to exit the isolate"); |
| 106 return new Future.value(null); |
| 107 } |
| 108 return debugger.isolate.stepInto(); |
| 109 } else { |
| 110 debugger.console.printLine('The program is already running'); |
| 111 return new Future.value(null); |
| 112 } |
| 113 } |
| 114 } |
| 115 |
| 116 class FinishCommand extends DebuggerCommand { |
| 117 FinishCommand(Debugger debugger) : super(debugger, 'finish', []); |
| 118 |
| 119 Future run(List<String> args) { |
| 120 if (debugger.isolatePaused()) { |
| 121 return debugger.isolate.stepOut(); |
| 122 } else { |
| 123 debugger.console.printLine('The program is already running'); |
| 124 return new Future.value(null); |
| 125 } |
| 126 } |
| 127 } |
| 128 |
| 129 // TODO(turnidge): Add argument completion. |
| 130 class DeleteCommand extends DebuggerCommand { |
| 131 DeleteCommand(Debugger debugger) : super(debugger, 'delete', []); |
| 132 |
| 133 Future run(List<String> args) { |
| 134 if (args.length < 1) { |
| 135 debugger.console.printLine('delete expects one or more arguments'); |
| 136 return new Future.value(null); |
| 137 } |
| 138 List toDelete = []; |
| 139 for (var arg in args) { |
| 140 int id = int.parse(arg); |
| 141 var bpt = null; |
| 142 for (var candidate in debugger.isolate.breakpoints) { |
| 143 if (candidate['breakpointNumber'] == id) { |
| 144 bpt = candidate; |
| 145 break; |
| 146 } |
| 147 } |
| 148 if (bpt == null) { |
| 149 debugger.console.printLine("Invalid breakpoint id '${id}'"); |
| 150 return new Future.value(null); |
| 151 } |
| 152 toDelete.add(bpt); |
| 153 } |
| 154 List pending = []; |
| 155 for (var bpt in toDelete) { |
| 156 pending.add(debugger.isolate.removeBreakpoint(bpt).then((_) { |
| 157 var id = bpt['breakpointNumber']; |
| 158 debugger.console.printLine("Removed breakpoint $id"); |
| 159 })); |
| 160 } |
| 161 return Future.wait(pending); |
| 162 } |
| 163 } |
| 164 |
| 165 class InfoBreakpointsCommand extends DebuggerCommand { |
| 166 InfoBreakpointsCommand(Debugger debugger) |
| 167 : super(debugger, 'breakpoints', []); |
| 168 |
| 169 Future run(List<String> args) { |
| 170 return debugger.isolate.reloadBreakpoints().then((_) { |
| 171 if (debugger.isolate.breakpoints.isEmpty) { |
| 172 debugger.console.printLine('No breakpoints'); |
| 173 } |
| 174 for (var bpt in debugger.isolate.breakpoints) { |
| 175 var bpId = bpt['breakpointNumber']; |
| 176 var script = bpt['location']['script']; |
| 177 var tokenPos = bpt['location']['tokenPos']; |
| 178 var line = script.tokenToLine(tokenPos); |
| 179 var col = script.tokenToCol(tokenPos); |
| 180 debugger.console.printLine( |
| 181 'Breakpoint ${bpId} at ${script.name}:${line}:${col}'); |
| 182 } |
| 183 }); |
| 184 } |
| 185 } |
| 186 |
| 187 class InfoIsolatesCommand extends DebuggerCommand { |
| 188 InfoIsolatesCommand(Debugger debugger) : super(debugger, 'isolates', []); |
| 189 |
| 190 Future run(List<String> args) { |
| 191 for (var isolate in debugger.isolate.vm.isolates) { |
| 192 debugger.console.printLine( |
| 193 "Isolate ${isolate.id} '${isolate.name}'"); |
| 194 } |
| 195 return new Future.value(null); |
| 196 } |
| 197 } |
| 198 |
| 199 class InfoCommand extends DebuggerCommand { |
| 200 InfoCommand(Debugger debugger) : super(debugger, 'info', [ |
| 201 new InfoBreakpointsCommand(debugger), |
| 202 new InfoIsolatesCommand(debugger), |
| 203 ]); |
| 204 |
| 205 Future run(List<String> args) { |
| 206 debugger.console.printLine("Invalid info command"); |
| 207 return new Future.value(null); |
| 208 } |
| 209 } |
| 210 |
| 211 class RefreshCoverageCommand extends DebuggerCommand { |
| 212 RefreshCoverageCommand(Debugger debugger) : super(debugger, 'coverage', []); |
| 213 |
| 214 Future run(List<String> args) { |
| 215 Set<Script> scripts = debugger.stackElement.activeScripts(); |
| 216 List pending = []; |
| 217 for (var script in scripts) { |
| 218 pending.add(script.refreshCoverage().then((_) { |
| 219 debugger.console.printLine('Refreshed coverage for ${script.name}'); |
| 220 })); |
| 221 } |
| 222 return Future.wait(pending); |
| 223 } |
| 224 } |
| 225 |
| 226 class RefreshCommand extends DebuggerCommand { |
| 227 RefreshCommand(Debugger debugger) : super(debugger, 'refresh', [ |
| 228 new RefreshCoverageCommand(debugger), |
| 229 ]); |
| 230 |
| 231 Future run(List<String> args) { |
| 232 return debugger.refreshStack(); |
| 233 } |
| 234 } |
| 235 |
| 236 // Tracks the state for an isolate debugging session. |
| 237 class Debugger { |
| 238 RootCommand cmd; |
| 239 DebuggerConsoleElement console; |
| 240 DebuggerStackElement stackElement; |
| 241 ServiceMap stack; |
| 242 |
| 243 Debugger() { |
| 244 cmd = new RootCommand([ |
| 245 new HelpCommand(this), |
| 246 new PauseCommand(this), |
| 247 new ContinueCommand(this), |
| 248 new NextCommand(this), |
| 249 new StepCommand(this), |
| 250 new FinishCommand(this), |
| 251 new DeleteCommand(this), |
| 252 new InfoCommand(this), |
| 253 new RefreshCommand(this), |
| 254 ]); |
| 255 } |
| 256 |
| 257 void set isolate(Isolate iso) { |
| 258 _isolate = iso; |
| 259 if (_isolate != null) { |
| 260 _isolate.reload().then((_) { |
| 261 _isolate.vm.events.stream.listen(_onEvent); |
| 262 _refreshStack(isolate.pauseEvent).then((_) { |
| 263 reportStatus(); |
| 264 }); |
| 265 }); |
| 266 } |
| 267 } |
| 268 Isolate get isolate => _isolate; |
| 269 Isolate _isolate; |
| 270 |
| 271 void init() { |
| 272 console.newline(); |
| 273 console.printBold("Type 'h' for help"); |
| 274 } |
| 275 |
| 276 Future refreshStack() { |
| 277 return _refreshStack(isolate.pauseEvent).then((_) { |
| 278 reportStatus(); |
| 279 }); |
| 280 } |
| 281 |
| 282 bool isolatePaused() { |
| 283 // TODO(turnidge): Stop relying on the isolate to track the last |
| 284 // pause event. Since we listen to events directly in the |
| 285 // debugger, this could introduce a race. |
| 286 return isolate.pauseEvent != null; |
| 287 } |
| 288 |
| 289 void warnOutOfDate() { |
| 290 // Wait a bit, then tell the user that the stack may be out of date. |
| 291 new Timer(const Duration(seconds:2), () { |
| 292 if (!isolatePaused()) { |
| 293 stackElement.isSampled = true; |
| 294 } |
| 295 }); |
| 296 } |
| 297 |
| 298 Future<ServiceMap> _refreshStack(ServiceEvent pauseEvent) { |
| 299 return isolate.getStack().then((result) { |
| 300 stack = result; |
| 301 // TODO(turnidge): Replace only the changed part of the stack to |
| 302 // reduce flicker. |
| 303 // stackElement.stack = stack; |
| 304 stackElement.updateStack(stack, pauseEvent); |
| 305 }); |
| 306 } |
| 307 |
| 308 void reportStatus() { |
| 309 if (_isolate.idle) { |
| 310 console.printLine('Isolate is idle'); |
| 311 } else if (_isolate.running) { |
| 312 console.printLine("Isolate is running (type 'pause' to interrupt)"); |
| 313 } else if (_isolate.pauseEvent != null) { |
| 314 _reportPause(_isolate.pauseEvent); |
| 315 } else { |
| 316 console.printLine('Isolate is in unknown state'); |
| 317 } |
| 318 } |
| 319 |
| 320 void _reportPause(ServiceEvent event) { |
| 321 if (event.eventType == 'IsolateCreated') { |
| 322 console.printLine( |
| 323 "Paused at isolate start (type 'continue' to start the isolate')"); |
| 324 } else if (event.eventType == 'IsolateShutdown') { |
| 325 console.printLine( |
| 326 "Paused at isolate exit (type 'continue' to exit the isolate')"); |
| 327 } |
| 328 if (stack['frames'].length > 0) { |
| 329 var frame = stack['frames'][0]; |
| 330 var script = frame['script']; |
| 331 script.load().then((_) { |
| 332 var line = script.tokenToLine(frame['tokenPos']); |
| 333 var col = script.tokenToCol(frame['tokenPos']); |
| 334 if (event.breakpoint != null) { |
| 335 var bpId = event.breakpoint['breakpointNumber']; |
| 336 console.printLine('Breakpoint ${bpId} at ${script.name}:${line}:${col}
'); |
| 337 } else if (event.exception != null) { |
| 338 // TODO(turnidge): Test this. |
| 339 console.printLine( |
| 340 'Exception ${event.exception} at ${script.name}:${line}:${col}'); |
| 341 } else { |
| 342 console.printLine('Paused at ${script.name}:${line}:${col}'); |
| 343 } |
| 344 }); |
| 345 } |
| 346 } |
| 347 |
| 348 void _onEvent(ServiceEvent event) { |
| 349 if (event.owner != isolate) { |
| 350 return; |
| 351 } |
| 352 switch(event.eventType) { |
| 353 case 'IsolateShutdown': |
| 354 console.printLine('Isolate shutdown'); |
| 355 isolate = null; |
| 356 break; |
| 357 |
| 358 case 'BreakpointReached': |
| 359 case 'IsolateInterrupted': |
| 360 case 'ExceptionThrown': |
| 361 _refreshStack(event).then((_) { |
| 362 _reportPause(event); |
| 363 }); |
| 364 break; |
| 365 |
| 366 case 'IsolateResumed': |
| 367 console.printLine('Continuing...'); |
| 368 break; |
| 369 |
| 370 case '_Graph': |
| 371 case 'BreakpointResolved': |
| 372 case 'IsolateCreated': |
| 373 case 'GC': |
| 374 // Ignore these events for now. |
| 375 break; |
| 376 |
| 377 default: |
| 378 console.printLine('Unrecognized event: $event'); |
| 379 break; |
| 380 } |
| 381 } |
| 382 |
| 383 String complete(String line) { |
| 384 List<String> completions = cmd.completeCommand(line); |
| 385 if (completions.length == 0) { |
| 386 // No completions. Leave the line alone. |
| 387 return line; |
| 388 } else if (completions.length == 1) { |
| 389 // Unambiguous completion. |
| 390 return completions[0]; |
| 391 } else { |
| 392 // Ambigous completion. |
| 393 completions = completions.map((s )=> s.trimRight()).toList(); |
| 394 completions.sort(); |
| 395 console.printBold(completions.toString()); |
| 396 |
| 397 // TODO(turnidge): Complete to common prefix of all completions. |
| 398 return line; |
| 399 } |
| 400 } |
| 401 |
| 402 // TODO(turnidge): Implement real command line history. |
| 403 String lastCommand; |
| 404 bool busy = false; |
| 405 |
| 406 Future run(String command) { |
| 407 assert(!busy); |
| 408 busy = true; |
| 409 if (command == '') { |
| 410 command = lastCommand; |
| 411 } |
| 412 lastCommand = command; |
| 413 console.printBold('\$ $command'); |
| 414 return cmd.runCommand(command).then((_) { |
| 415 busy = false; |
| 416 }).catchError((e) { |
| 417 console.printLine('ERROR $e'); |
| 418 }); |
| 419 } |
| 420 } |
| 421 |
| 12 @CustomTag('debugger-page') | 422 @CustomTag('debugger-page') |
| 13 class DebuggerPageElement extends ObservatoryElement { | 423 class DebuggerPageElement extends ObservatoryElement { |
| 14 @published Isolate isolate; | 424 @published Isolate isolate; |
| 15 @published bool showConsole = false; | 425 |
| 426 isolateChanged(oldValue) { |
| 427 if (isolate != null) { |
| 428 debugger.isolate = isolate; |
| 429 } |
| 430 } |
| 431 Debugger debugger = new Debugger(); |
| 16 | 432 |
| 17 DebuggerPageElement.created() : super.created(); | 433 DebuggerPageElement.created() : super.created(); |
| 18 | 434 |
| 19 @override | 435 @override |
| 20 void attached() { | 436 void attached() { |
| 21 super.attached(); | 437 super.attached(); |
| 22 | 438 |
| 23 // TODO(turnidge): Get these values from the DOM. | 439 var navbarDiv = $['navbarDiv']; |
| 24 // TODO(turnidge): splitterHeight is 0 until I implement it. | 440 var stackDiv = $['stackDiv']; |
| 25 const int navbarHeight = 56; | 441 var splitterDiv = $['splitterDiv']; |
| 26 const int splitterHeight = 0; | 442 var cmdDiv = $['commandDiv']; |
| 27 const int cmdHeight = 22; | 443 var consoleDiv = $['consoleDiv']; |
| 28 | 444 |
| 29 var stack = $['stack']; | 445 int navbarHeight = navbarDiv.clientHeight; |
| 446 int splitterHeight = splitterDiv.clientHeight; |
| 447 int cmdHeight = cmdDiv.clientHeight; |
| 448 |
| 30 int windowHeight = window.innerHeight; | 449 int windowHeight = window.innerHeight; |
| 31 int available = windowHeight - (navbarHeight + splitterHeight); | 450 int fixedHeight = navbarHeight + splitterHeight + cmdHeight; |
| 32 int stackHeight = available ~/ 1.3; | 451 int available = windowHeight - fixedHeight; |
| 33 if (showConsole) { | 452 int stackHeight = available ~/ 1.6; |
| 34 stack.style.setProperty('height', '${stackHeight}px'); | 453 stackDiv.style.setProperty('height', '${stackHeight}px'); |
| 35 } else { | 454 |
| 36 stack.style.setProperty('height', '${available}px'); | 455 // Wire the debugger object to the stack, console, and command line. |
| 37 } | 456 var stackElement = $['stackElement']; |
| 457 debugger.stackElement = stackElement; |
| 458 stackElement.debugger = debugger; |
| 459 debugger.console = $['console']; |
| 460 $['commandline'].debugger = debugger; |
| 461 debugger.init(); |
| 38 } | 462 } |
| 463 |
| 39 } | 464 } |
| 40 | 465 |
| 41 @CustomTag('debugger-stack') | 466 @CustomTag('debugger-stack') |
| 42 class DebuggerStackElement extends ObservatoryElement { | 467 class DebuggerStackElement extends ObservatoryElement { |
| 43 @published Isolate isolate; | 468 @published Isolate isolate; |
| 44 @published ServiceMap stack; | 469 @observable bool hasStack = false; |
| 45 @published int activeFrame = 0; | 470 @observable bool isSampled = false; |
| 471 Debugger debugger = null; |
| 46 | 472 |
| 47 isolateChanged(oldValue) { | 473 _addFrame(List frameList, ObservableMap frameInfo, bool expand) { |
| 48 isolate.getStack().then((result) { | 474 DebuggerFrameElement frameElement = new Element.tag('debugger-frame'); |
| 49 stack = result; | 475 frameElement.expand = expand; |
| 50 }); | 476 frameElement.frame = frameInfo; |
| 477 |
| 478 var li = new LIElement(); |
| 479 li.classes.add('list-group-item'); |
| 480 li.children.insert(0, frameElement); |
| 481 |
| 482 frameList.insert(0, li); |
| 483 } |
| 484 |
| 485 void updateStack(ServiceMap newStack, ServiceEvent pauseEvent) { |
| 486 List frameElements = $['frameList'].children; |
| 487 List newFrames = newStack['frames']; |
| 488 |
| 489 // Remove any frames whose functions don't match, starting from |
| 490 // bottom of stack. |
| 491 int oldPos = frameElements.length - 1; |
| 492 int newPos = newFrames.length - 1; |
| 493 while (oldPos >= 0 && newPos >= 0) { |
| 494 if (!frameElements[oldPos].children[0].matchFrame(newFrames[newPos])) { |
| 495 // The rest of the frame elements no longer match. Remove them. |
| 496 for (int i = 0; i <= oldPos; i++) { |
| 497 // NOTE(turnidge): removeRange is missing, sadly. |
| 498 frameElements.removeAt(0); |
| 499 } |
| 500 break; |
| 501 } |
| 502 oldPos--; |
| 503 newPos--; |
| 504 } |
| 505 |
| 506 // Remove any extra frames. |
| 507 if (frameElements.length > newFrames.length) { |
| 508 // Remove old frames from the top of stack. |
| 509 int removeCount = frameElements.length - newFrames.length; |
| 510 for (int i = 0; i < removeCount; i++) { |
| 511 frameElements.removeAt(0); |
| 512 } |
| 513 } |
| 514 |
| 515 // Add any new frames. |
| 516 int newCount = 0; |
| 517 if (frameElements.length < newFrames.length) { |
| 518 // Add new frames to the top of stack. |
| 519 newCount = newFrames.length - frameElements.length; |
| 520 for (int i = newCount-1; i >= 0; i--) { |
| 521 _addFrame(frameElements, newFrames[i], i == 0); |
| 522 } |
| 523 } |
| 524 assert(frameElements.length == newFrames.length); |
| 525 |
| 526 if (frameElements.isNotEmpty) { |
| 527 frameElements[0].children[0].expand = true; |
| 528 for (int i = newCount; i < frameElements.length; i++) { |
| 529 frameElements[i].children[0].updateFrame(newFrames[i]); |
| 530 } |
| 531 } |
| 532 |
| 533 isSampled = pauseEvent == null; |
| 534 hasStack = frameElements.isNotEmpty; |
| 535 } |
| 536 |
| 537 Set<Script> activeScripts() { |
| 538 var s = new Set<Script>(); |
| 539 List frameElements = $['frameList'].children; |
| 540 for (var frameElement in frameElements) { |
| 541 s.add(frameElement.children[0].script); |
| 542 } |
| 543 return s; |
| 544 } |
| 545 |
| 546 doPauseIsolate(_) { |
| 547 if (debugger != null) { |
| 548 return debugger.isolate.pause(); |
| 549 } else { |
| 550 return new Future.value(null); |
| 551 } |
| 552 } |
| 553 |
| 554 doRefreshStack(_) { |
| 555 if (debugger != null) { |
| 556 return debugger.refreshStack(); |
| 557 } else { |
| 558 return new Future.value(null); |
| 559 } |
| 51 } | 560 } |
| 52 | 561 |
| 53 DebuggerStackElement.created() : super.created(); | 562 DebuggerStackElement.created() : super.created(); |
| 54 } | 563 } |
| 55 | 564 |
| 56 @CustomTag('debugger-frame') | 565 @CustomTag('debugger-frame') |
| 57 class DebuggerFrameElement extends ObservatoryElement { | 566 class DebuggerFrameElement extends ObservatoryElement { |
| 58 @published ObservableMap frame; | 567 @published ObservableMap frame; |
| 59 | 568 |
| 60 // When true, the frame will start out expanded. | 569 // When true, the frame will start out expanded. |
| 61 @published bool expand = false; | 570 @published bool expand = false; |
| 62 | 571 |
| 63 @observable String scriptHeight; | 572 @observable String scriptHeight; |
| 64 @observable bool expanded = false; | 573 @observable bool expanded = false; |
| 65 @observable bool busy = false; | 574 @observable bool busy = false; |
| 66 | 575 |
| 67 DebuggerFrameElement.created() : super.created(); | 576 DebuggerFrameElement.created() : super.created(); |
| 68 | 577 |
| 578 bool matchFrame(ObservableMap newFrame) { |
| 579 return newFrame['function'].id == frame['function'].id; |
| 580 } |
| 581 |
| 582 void updateFrame(ObservableMap newFrame) { |
| 583 assert(matchFrame(newFrame)); |
| 584 frame['depth'] = newFrame['depth']; |
| 585 frame['tokenPos'] = newFrame['tokenPos']; |
| 586 frame['vars'] = newFrame['vars']; |
| 587 } |
| 588 |
| 589 Script get script => frame['script']; |
| 590 |
| 69 @override | 591 @override |
| 70 void attached() { | 592 void attached() { |
| 71 super.attached(); | 593 super.attached(); |
| 72 int windowHeight = window.innerHeight; | 594 int windowHeight = window.innerHeight; |
| 73 scriptHeight = '${windowHeight ~/ 1.6}px'; | 595 scriptHeight = '${windowHeight ~/ 1.6}px'; |
| 74 } | 596 } |
| 75 | 597 |
| 76 void expandChanged(oldValue) { | 598 void expandChanged(oldValue) { |
| 77 if (expand != expanded) { | 599 if (expand != expanded) { |
| 78 toggleExpand(null, null, null); | 600 toggleExpand(null, null, null); |
| (...skipping 16 matching lines...) Expand all Loading... |
| 95 busy = false; | 617 busy = false; |
| 96 }); | 618 }); |
| 97 } | 619 } |
| 98 } | 620 } |
| 99 | 621 |
| 100 @CustomTag('debugger-console') | 622 @CustomTag('debugger-console') |
| 101 class DebuggerConsoleElement extends ObservatoryElement { | 623 class DebuggerConsoleElement extends ObservatoryElement { |
| 102 @published Isolate isolate; | 624 @published Isolate isolate; |
| 103 | 625 |
| 104 DebuggerConsoleElement.created() : super.created(); | 626 DebuggerConsoleElement.created() : super.created(); |
| 627 |
| 628 void printLine(String line) { |
| 629 var div = new DivElement(); |
| 630 div.classes.add('normal'); |
| 631 div.appendText(line); |
| 632 $['consoleText'].children.add(div); |
| 633 div.scrollIntoView(); |
| 634 } |
| 635 |
| 636 void printBold(String line) { |
| 637 var div = new DivElement(); |
| 638 div.classes.add('bold'); |
| 639 div.appendText(line); |
| 640 $['consoleText'].children.add(div); |
| 641 div.scrollIntoView(); |
| 642 } |
| 643 |
| 644 void newline() { |
| 645 var br = new BRElement(); |
| 646 $['consoleText'].children.add(br); |
| 647 br.scrollIntoView(); |
| 648 } |
| 105 } | 649 } |
| 106 | 650 |
| 107 @CustomTag('debugger-input') | 651 @CustomTag('debugger-input') |
| 108 class DebuggerInputElement extends ObservatoryElement { | 652 class DebuggerInputElement extends ObservatoryElement { |
| 109 @published Isolate isolate; | 653 @published Isolate isolate; |
| 110 @published String text = ''; | 654 @published String text = ''; |
| 655 @observable Debugger debugger; |
| 111 | 656 |
| 112 @override | 657 @override |
| 113 void ready() { | 658 void ready() { |
| 114 super.ready(); | 659 super.ready(); |
| 115 var textBox = $['textBox']; | 660 var textBox = $['textBox']; |
| 116 textBox.select(); | 661 textBox.select(); |
| 117 textBox.onKeyDown.listen((KeyboardEvent e) { | 662 textBox.onKeyDown.listen((KeyboardEvent e) { |
| 118 switch (e.keyCode) { | 663 switch (e.keyCode) { |
| 119 case KeyCode.TAB: | 664 case KeyCode.TAB: |
| 120 e.preventDefault(); | 665 e.preventDefault(); |
| 121 textBox.setRangeText('TAB'); | 666 int cursorPos = textBox.selectionStart; |
| 122 textBox.setSelectionRange(textBox.selectionStart + 3, | 667 var completion = debugger.complete(text.substring(0, cursorPos)); |
| 123 textBox.selectionStart + 3); | 668 text = completion + text.substring(cursorPos); |
| 669 // TODO(turnidge): Move the cursor to the end of the |
| 670 // completion, rather than the end of the string. |
| 124 break; | 671 break; |
| 125 case KeyCode.ENTER: | 672 case KeyCode.ENTER: |
| 126 print('Debugger command (not implemented): $text'); | 673 if (!debugger.busy) { |
| 127 text = ''; | 674 debugger.run(text); |
| 675 text = ''; |
| 676 } |
| 128 break; | 677 break; |
| 129 } | 678 } |
| 130 }); | 679 }); |
| 131 } | 680 } |
| 132 | 681 |
| 133 DebuggerInputElement.created() : super.created(); | 682 DebuggerInputElement.created() : super.created(); |
| 134 } | 683 } |
| 135 | 684 |
| OLD | NEW |