Chromium Code Reviews| 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 trydart.interaction_manager; | 5 library trydart.interaction_manager; |
| 6 | 6 |
| 7 import 'dart:html'; | 7 import 'dart:html'; |
| 8 | 8 |
| 9 import 'dart:convert' show | 9 import 'dart:convert' show |
| 10 JSON; | 10 JSON; |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 73 import 'shadow_root.dart' show | 73 import 'shadow_root.dart' show |
| 74 getShadowRoot, | 74 getShadowRoot, |
| 75 getText, | 75 getText, |
| 76 removeShadowRootPolyfill, | 76 removeShadowRootPolyfill, |
| 77 setShadowRoot; | 77 setShadowRoot; |
| 78 | 78 |
| 79 const String TRY_DART_NEW_DEFECT = | 79 const String TRY_DART_NEW_DEFECT = |
| 80 'https://code.google.com/p/dart/issues/entry' | 80 'https://code.google.com/p/dart/issues/entry' |
| 81 '?template=Try+Dart+Internal+Error'; | 81 '?template=Try+Dart+Internal+Error'; |
| 82 | 82 |
| 83 const Duration HEARTBEAT_INTERVAL = const Duration(milliseconds: 500); | 83 const Duration HEARTBEAT_INTERVAL = const Duration(milliseconds: 50); |
|
Johnni Winther
2014/06/23 14:50:01
Add comments on these constants.
ahe
2014/06/23 15:15:54
Done.
| |
| 84 | 84 |
| 85 const Duration SAVE_INTERVAL = const Duration(seconds: 5); | 85 const Duration SAVE_INTERVAL = const Duration(seconds: 5); |
| 86 | 86 |
| 87 const Duration COMPILE_INTERVAL = const Duration(seconds: 1); | 87 const Duration COMPILE_INTERVAL = const Duration(seconds: 1); |
| 88 | 88 |
| 89 const Duration SLOW_COMPILE = const Duration(seconds: 1); | |
| 90 | |
| 89 /** | 91 /** |
| 90 * UI interaction manager for the entire application. | 92 * UI interaction manager for the entire application. |
| 91 */ | 93 */ |
| 92 abstract class InteractionManager { | 94 abstract class InteractionManager { |
| 93 // Design note: All UI interactions go through one instance of this | 95 // Design note: All UI interactions go through one instance of this |
| 94 // class. This is by design. | 96 // class. This is by design. |
| 95 // | 97 // |
| 96 // Simplicity in UI is in the eye of the beholder, not the implementor. Great | 98 // Simplicity in UI is in the eye of the beholder, not the implementor. Great |
| 97 // 'natural UI' is usually achieved with substantial implementation | 99 // 'natural UI' is usually achieved with substantial implementation |
| 98 // complexity that doesn't modularize well and has nasty complicated state | 100 // complexity that doesn't modularize well and has nasty complicated state |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 129 /// Called when the user selected a new project file. | 131 /// Called when the user selected a new project file. |
| 130 void onProjectFileSelected(String projectFile); | 132 void onProjectFileSelected(String projectFile); |
| 131 | 133 |
| 132 /// Called when notified about a project file changed (on the server). | 134 /// Called when notified about a project file changed (on the server). |
| 133 void onProjectFileFsEvent(MessageEvent e); | 135 void onProjectFileFsEvent(MessageEvent e); |
| 134 | 136 |
| 135 /// Called every 500ms. | 137 /// Called every 500ms. |
| 136 void onHeartbeat(Timer timer); | 138 void onHeartbeat(Timer timer); |
| 137 | 139 |
| 138 /// Called by [:window.onMessage.listen:]. | 140 /// Called by [:window.onMessage.listen:]. |
| 139 void onMessage(MessageEvent event); | 141 void onWindowMessage(MessageEvent event); |
| 142 | |
| 143 void onCompilationFailed(); | |
| 140 | 144 |
| 141 void onCompilationDone(); | 145 void onCompilationDone(); |
| 142 | 146 |
| 143 /// Called when a compilation is starting, but just before sending the | 147 /// Called when a compilation is starting, but just before sending the |
| 144 /// initiating message to the compiler isolate. | 148 /// initiating message to the compiler isolate. |
| 145 void compilationStarting(); | 149 void compilationStarting(); |
| 150 | |
| 151 // TODO(ahe): Remove this from InteractionManager, but not from InitialState. | |
| 152 void consolePrintLine(line); | |
| 153 | |
| 154 void verboseCompilerMessage(String message); | |
| 146 } | 155 } |
| 147 | 156 |
| 148 /** | 157 /** |
| 149 * State machine for UI interactions. | 158 * State machine for UI interactions. |
| 150 */ | 159 */ |
| 151 class InteractionContext extends InteractionManager { | 160 class InteractionContext extends InteractionManager { |
| 152 InteractionState state; | 161 InteractionState state; |
| 153 | 162 |
| 154 final Map<String, CompilationUnit> projectFiles = <String, CompilationUnit>{}; | 163 final Map<String, CompilationUnit> projectFiles = <String, CompilationUnit>{}; |
| 155 | 164 |
| 156 final Set<CompilationUnit> modifiedUnits = new Set<CompilationUnit>(); | 165 final Set<CompilationUnit> modifiedUnits = new Set<CompilationUnit>(); |
| 157 | 166 |
| 158 final Queue<CompilationUnit> unitsToSave = new Queue<CompilationUnit>(); | 167 final Queue<CompilationUnit> unitsToSave = new Queue<CompilationUnit>(); |
| 159 | 168 |
| 169 /// Tracks time since last modification of a "project" file. | |
| 160 final Stopwatch saveTimer = new Stopwatch(); | 170 final Stopwatch saveTimer = new Stopwatch(); |
| 161 | 171 |
| 172 /// Tracks time since last modification. | |
| 162 final Stopwatch compileTimer = new Stopwatch(); | 173 final Stopwatch compileTimer = new Stopwatch(); |
| 163 | 174 |
| 175 /// Tracks elapsed time of current compilation. | |
| 176 final Stopwatch elapsedCompilationTime = new Stopwatch(); | |
| 177 | |
| 164 CompilationUnit currentCompilationUnit = | 178 CompilationUnit currentCompilationUnit = |
| 165 // TODO(ahe): Don't use a fake unit. | 179 // TODO(ahe): Don't use a fake unit. |
| 166 new CompilationUnit('fake', ''); | 180 new CompilationUnit('fake', ''); |
| 167 | 181 |
| 168 Timer heartbeat; | 182 Timer heartbeat; |
| 169 | 183 |
| 170 Completer<String> completeSaveOperation; | 184 Completer<String> completeSaveOperation; |
| 171 | 185 |
| 186 bool shouldClearConsole = false; | |
| 187 | |
| 188 Element compilerConsole; | |
| 189 | |
| 190 bool isFirstCompile = true; | |
| 191 | |
| 172 final Set<AnchorElement> oldDiagnostics = new Set<AnchorElement>(); | 192 final Set<AnchorElement> oldDiagnostics = new Set<AnchorElement>(); |
| 173 | 193 |
| 174 InteractionContext() | 194 InteractionContext() |
| 175 : super.internal() { | 195 : super.internal() { |
| 176 state = new InitialState(this); | 196 state = new InitialState(this); |
| 177 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); | 197 heartbeat = new Timer.periodic(HEARTBEAT_INTERVAL, onHeartbeat); |
| 178 } | 198 } |
| 179 | 199 |
| 180 void onInput(Event event) => state.onInput(event); | 200 void onInput(Event event) => state.onInput(event); |
| 181 | 201 |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 222 void onProjectFileSelected(String projectFile) { | 242 void onProjectFileSelected(String projectFile) { |
| 223 return state.onProjectFileSelected(projectFile); | 243 return state.onProjectFileSelected(projectFile); |
| 224 } | 244 } |
| 225 | 245 |
| 226 void onProjectFileFsEvent(MessageEvent e) { | 246 void onProjectFileFsEvent(MessageEvent e) { |
| 227 return state.onProjectFileFsEvent(e); | 247 return state.onProjectFileFsEvent(e); |
| 228 } | 248 } |
| 229 | 249 |
| 230 void onHeartbeat(Timer timer) => state.onHeartbeat(timer); | 250 void onHeartbeat(Timer timer) => state.onHeartbeat(timer); |
| 231 | 251 |
| 232 void onMessage(MessageEvent event) => state.onMessage(event); | 252 void onWindowMessage(MessageEvent event) => state.onWindowMessage(event); |
| 253 | |
| 254 void onCompilationFailed() => state.onCompilationFailed(); | |
| 233 | 255 |
| 234 void onCompilationDone() => state.onCompilationDone(); | 256 void onCompilationDone() => state.onCompilationDone(); |
| 235 | 257 |
| 236 void compilationStarting() => state.compilationStarting(); | 258 void compilationStarting() => state.compilationStarting(); |
| 259 | |
| 260 void consolePrintLine(line) => state.consolePrintLine(line); | |
| 261 | |
| 262 void verboseCompilerMessage(String message) { | |
| 263 return state.verboseCompilerMessage(message); | |
| 264 } | |
| 237 } | 265 } |
| 238 | 266 |
| 239 abstract class InteractionState implements InteractionManager { | 267 abstract class InteractionState implements InteractionManager { |
| 240 InteractionContext get context; | 268 InteractionContext get context; |
| 241 | 269 |
| 242 // TODO(ahe): Remove this. | 270 // TODO(ahe): Remove this. |
| 243 Set<AnchorElement> get oldDiagnostics { | 271 Set<AnchorElement> get oldDiagnostics { |
| 244 throw 'Use context.oldDiagnostics instead'; | 272 throw 'Use context.oldDiagnostics instead'; |
| 245 } | 273 } |
| 246 | 274 |
| (...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 519 saveUnits(); | 547 saveUnits(); |
| 520 } | 548 } |
| 521 if (!settings.compilationPaused && | 549 if (!settings.compilationPaused && |
| 522 context.compileTimer.elapsed > COMPILE_INTERVAL) { | 550 context.compileTimer.elapsed > COMPILE_INTERVAL) { |
| 523 if (startCompilation()) { | 551 if (startCompilation()) { |
| 524 context.compileTimer | 552 context.compileTimer |
| 525 ..stop() | 553 ..stop() |
| 526 ..reset(); | 554 ..reset(); |
| 527 } | 555 } |
| 528 } | 556 } |
| 557 | |
| 558 if (context.elapsedCompilationTime.elapsed > SLOW_COMPILE) { | |
| 559 if (context.compilerConsole.parent == null) { | |
| 560 outputDiv.append(context.compilerConsole); | |
| 561 } | |
| 562 } | |
| 529 } | 563 } |
| 530 | 564 |
| 531 void saveUnits() { | 565 void saveUnits() { |
| 532 if (context.unitsToSave.isEmpty) return; | 566 if (context.unitsToSave.isEmpty) return; |
| 533 CompilationUnit unit = context.unitsToSave.removeFirst(); | 567 CompilationUnit unit = context.unitsToSave.removeFirst(); |
| 534 onError(ProgressEvent event) { | 568 onError(ProgressEvent event) { |
| 535 HttpRequest request = event.target; | 569 HttpRequest request = event.target; |
| 536 statusDiv.text = "Couldn't save '${unit.name}': ${request.responseText}"; | 570 statusDiv.text = "Couldn't save '${unit.name}': ${request.responseText}"; |
| 537 context.completeSaveOperation.complete(unit.name); | 571 context.completeSaveOperation.complete(unit.name); |
| 538 } | 572 } |
| 539 new HttpRequest() | 573 new HttpRequest() |
| 540 ..open("POST", "/project/${unit.name}") | 574 ..open("POST", "/project/${unit.name}") |
| 541 ..onError.listen(onError) | 575 ..onError.listen(onError) |
| 542 ..send(unit.content); | 576 ..send(unit.content); |
| 543 void setupCompleter() { | 577 void setupCompleter() { |
| 544 context.completeSaveOperation = new Completer<String>.sync(); | 578 context.completeSaveOperation = new Completer<String>.sync(); |
| 545 context.completeSaveOperation.future.then((String name) { | 579 context.completeSaveOperation.future.then((String name) { |
| 546 if (name == unit.name) { | 580 if (name == unit.name) { |
| 547 print("Saved source of '$name'"); | 581 print("Saved source of '$name'"); |
| 548 saveUnits(); | 582 saveUnits(); |
| 549 } else { | 583 } else { |
| 550 setupCompleter(); | 584 setupCompleter(); |
| 551 } | 585 } |
| 552 }); | 586 }); |
| 553 } | 587 } |
| 554 setupCompleter(); | 588 setupCompleter(); |
| 555 } | 589 } |
| 556 | 590 |
| 557 void onMessage(MessageEvent event) { | 591 void onWindowMessage(MessageEvent event) { |
| 558 if (event.source is! WindowBase || event.source == window) { | 592 if (event.source is! WindowBase || event.source == window) { |
| 559 return onBadMessage(event); | 593 return onBadMessage(event); |
| 560 } | 594 } |
| 561 if (event.data is List) { | 595 if (event.data is List) { |
| 562 List message = event.data; | 596 List message = event.data; |
| 563 if (message.length > 0) { | 597 if (message.length > 0) { |
| 564 switch (message[0]) { | 598 switch (message[0]) { |
| 565 case 'error': | 599 case 'error': |
| 566 return onErrorMessage(message[1]['url'], message[1]['message']); | 600 return onErrorMessage(message[1]['url'], message[1]['message']); |
| 567 case 'scrollHeight': | 601 case 'scrollHeight': |
| (...skipping 26 matching lines...) Expand all Loading... | |
| 594 } | 628 } |
| 595 | 629 |
| 596 void onBadMessage(MessageEvent event) { | 630 void onBadMessage(MessageEvent event) { |
| 597 window.console | 631 window.console |
| 598 ..groupCollapsed('Bad message') | 632 ..groupCollapsed('Bad message') |
| 599 ..dir(event) | 633 ..dir(event) |
| 600 ..log(event.source.runtimeType) | 634 ..log(event.source.runtimeType) |
| 601 ..groupEnd(); | 635 ..groupEnd(); |
| 602 } | 636 } |
| 603 | 637 |
| 604 void consolePrintLine(data) { | 638 void consolePrintLine(line) { |
| 605 outputDiv.appendText('$data\n'); | 639 if (context.shouldClearConsole) { |
| 640 context.shouldClearConsole = false; | |
| 641 outputDiv.nodes.clear(); | |
| 642 } | |
| 643 if (window.parent != window) { | |
| 644 // Test support. | |
| 645 // TODO(ahe): Use '/' instead of '*' when Firefox is upgraded to version | |
| 646 // 30 across build bots. Support for '/' was added in version 29, and we | |
| 647 // support the two most recent versions. | |
| 648 window.parent.postMessage('$line\n', '*'); | |
| 649 } | |
| 650 outputDiv.appendText('$line\n'); | |
| 651 } | |
| 652 | |
| 653 void onCompilationFailed() { | |
| 606 } | 654 } |
| 607 | 655 |
| 608 void onCompilationDone() { | 656 void onCompilationDone() { |
| 657 context.isFirstCompile = false; | |
| 658 context.elapsedCompilationTime.stop(); | |
| 659 Duration compilationDuration = context.elapsedCompilationTime.elapsed; | |
| 660 context.elapsedCompilationTime.reset(); | |
| 661 print('Compilation took $compilationDuration.'); | |
| 662 if (context.compilerConsole.parent != null) { | |
| 663 context.compilerConsole.remove(); | |
| 664 } | |
| 609 for (AnchorElement diagnostic in context.oldDiagnostics) { | 665 for (AnchorElement diagnostic in context.oldDiagnostics) { |
| 610 if (diagnostic.parent != null) { | 666 if (diagnostic.parent != null) { |
| 611 // Problem fixed, remove the diagnostic. | 667 // Problem fixed, remove the diagnostic. |
| 612 diagnostic.replaceWith(new Text(getText(diagnostic))); | 668 diagnostic.replaceWith(new Text(getText(diagnostic))); |
| 613 } | 669 } |
| 614 } | 670 } |
| 615 context.oldDiagnostics.clear(); | 671 context.oldDiagnostics.clear(); |
| 616 observer.takeRecords(); // Discard mutations. | 672 observer.takeRecords(); // Discard mutations. |
| 617 } | 673 } |
| 618 | 674 |
| 619 void compilationStarting() { | 675 void compilationStarting() { |
| 676 var progress = new SpanElement() | |
| 677 ..appendHtml('<i class="icon-spinner icon-spin"></i>') | |
| 678 ..appendText(' Compiling Dart program.'); | |
| 679 if (settings.verboseCompiler) { | |
| 680 progress.appendText('..'); | |
| 681 } | |
| 682 context.compilerConsole = new SpanElement() | |
| 683 ..append(progress) | |
| 684 ..appendText('\n'); | |
| 685 context.shouldClearConsole = true; | |
| 686 context.elapsedCompilationTime | |
| 687 ..start() | |
| 688 ..reset(); | |
| 689 if (context.isFirstCompile) { | |
| 690 outputDiv.append(context.compilerConsole); | |
| 691 } | |
| 620 context.oldDiagnostics | 692 context.oldDiagnostics |
| 621 ..clear() | 693 ..clear() |
| 622 ..addAll(mainEditorPane.querySelectorAll('a.diagnostic')); | 694 ..addAll(mainEditorPane.querySelectorAll('a.diagnostic')); |
| 623 } | 695 } |
| 696 | |
| 697 void verboseCompilerMessage(String message) { | |
| 698 if (settings.verboseCompiler) { | |
| 699 context.compilerConsole.appendText('$message\n'); | |
| 700 } else { | |
| 701 if (isCompilerStageMarker(message)) { | |
| 702 Element progress = context.compilerConsole.firstChild; | |
| 703 progress.appendText('.'); | |
| 704 } | |
| 705 } | |
| 706 } | |
| 624 } | 707 } |
| 625 | 708 |
| 626 Future<String> getString(uri) { | 709 Future<String> getString(uri) { |
| 627 return new Future<String>.sync(() => HttpRequest.getString('$uri')); | 710 return new Future<String>.sync(() => HttpRequest.getString('$uri')); |
| 628 } | 711 } |
| 629 | 712 |
| 630 class PendingInputState extends InitialState { | 713 class PendingInputState extends InitialState { |
| 631 PendingInputState(InteractionContext context) | 714 PendingInputState(InteractionContext context) |
| 632 : super(context); | 715 : super(context); |
| 633 | 716 |
| (...skipping 429 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1063 return text.split(new RegExp('^', multiLine: true)); | 1146 return text.split(new RegExp('^', multiLine: true)); |
| 1064 } | 1147 } |
| 1065 | 1148 |
| 1066 void removeCodeCompletion() { | 1149 void removeCodeCompletion() { |
| 1067 List<Node> highlighting = | 1150 List<Node> highlighting = |
| 1068 mainEditorPane.querySelectorAll('.dart-code-completion'); | 1151 mainEditorPane.querySelectorAll('.dart-code-completion'); |
| 1069 for (Element element in highlighting) { | 1152 for (Element element in highlighting) { |
| 1070 element.remove(); | 1153 element.remove(); |
| 1071 } | 1154 } |
| 1072 } | 1155 } |
| 1156 | |
| 1157 bool isCompilerStageMarker(String message) { | |
| 1158 return | |
| 1159 message.startsWith('Package root is ') || | |
| 1160 message.startsWith('Compiling ') || | |
| 1161 message == "Resolving..." || | |
| 1162 message.startsWith('Resolved ') || | |
| 1163 message == "Inferring types..." || | |
| 1164 message == "Compiling..." || | |
| 1165 message.startsWith('Compiled '); | |
| 1166 } | |
| OLD | NEW |