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 46 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 57 import 'selection.dart' show | 57 import 'selection.dart' show |
| 58 TrySelection, | 58 TrySelection, |
| 59 isCollapsed; | 59 isCollapsed; |
| 60 | 60 |
| 61 import 'editor.dart' as editor; | 61 import 'editor.dart' as editor; |
| 62 | 62 |
| 63 import 'mock.dart' as mock; | 63 import 'mock.dart' as mock; |
| 64 | 64 |
| 65 import 'settings.dart' as settings; | 65 import 'settings.dart' as settings; |
| 66 | 66 |
| 67 import 'shadow_root.dart' show | |
| 68 getShadowRoot, | |
| 69 removeShadowRootPolyfill, | |
| 70 setShadowRoot; | |
| 71 | |
| 67 const String TRY_DART_NEW_DEFECT = | 72 const String TRY_DART_NEW_DEFECT = |
| 68 'https://code.google.com/p/dart/issues/entry' | 73 'https://code.google.com/p/dart/issues/entry' |
| 69 '?template=Try+Dart+Internal+Error'; | 74 '?template=Try+Dart+Internal+Error'; |
| 70 | 75 |
| 71 /** | 76 /** |
| 72 * UI interaction manager for the entire application. | 77 * UI interaction manager for the entire application. |
| 73 */ | 78 */ |
| 74 abstract class InteractionManager { | 79 abstract class InteractionManager { |
| 75 // Design note: All UI interactions go through one instance of this | 80 // Design note: All UI interactions go through one instance of this |
| 76 // class. This is by design. | 81 // class. This is by design. |
| (...skipping 173 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 250 } else { | 255 } else { |
| 251 window.console | 256 window.console |
| 252 ..error('Unexpected node') | 257 ..error('Unexpected node') |
| 253 ..dir(node); | 258 ..dir(node); |
| 254 } | 259 } |
| 255 } | 260 } |
| 256 break; | 261 break; |
| 257 } | 262 } |
| 258 } | 263 } |
| 259 | 264 |
| 260 // editor.scheduleRemoveCodeCompletion(); | |
| 261 | |
| 262 // This is a hack to get Safari (iOS) to send mutation events on | 265 // This is a hack to get Safari (iOS) to send mutation events on |
| 263 // contenteditable. | 266 // contenteditable. |
| 264 // TODO(ahe): Move to onInput? | 267 // TODO(ahe): Move to onInput? |
| 265 var newDiv = new DivElement(); | 268 var newDiv = new DivElement(); |
| 266 hackDiv.replaceWith(newDiv); | 269 hackDiv.replaceWith(newDiv); |
| 267 hackDiv = newDiv; | 270 hackDiv = newDiv; |
| 268 } | 271 } |
| 269 | 272 |
| 270 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 273 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
| 271 print('onMutation'); | 274 print('onMutation'); |
| 272 | 275 |
| 273 List<Node> highlighting = mainEditorPane.querySelectorAll( | 276 removeCodeCompletion(); |
| 274 'a.diagnostic>span, .dart-code-completion, .hazed-suggestion'); | |
| 275 for (Element element in highlighting) { | |
| 276 element.remove(); | |
| 277 } | |
| 278 | 277 |
| 279 Selection selection = window.getSelection(); | 278 Selection selection = window.getSelection(); |
| 280 TrySelection trySelection = new TrySelection(mainEditorPane, selection); | 279 TrySelection trySelection = new TrySelection(mainEditorPane, selection); |
| 281 | 280 |
| 282 Set<Node> normalizedNodes = new Set<Node>(); | 281 Set<Node> normalizedNodes = new Set<Node>(); |
| 283 for (MutationRecord record in mutations) { | 282 for (MutationRecord record in mutations) { |
| 284 normalizeMutationRecord(record, trySelection, normalizedNodes); | 283 normalizeMutationRecord(record, trySelection, normalizedNodes); |
| 285 } | 284 } |
| 286 | 285 |
| 287 if (normalizedNodes.length == 1) { | 286 if (normalizedNodes.length == 1) { |
| 288 Node node = normalizedNodes.single; | 287 Node node = normalizedNodes.single; |
| 289 if (node is Element && node.classes.contains('lineNumber')) { | 288 if (node is Element && node.classes.contains('lineNumber')) { |
| 290 print('Single line change: ${node.outerHtml}'); | 289 print('Single line change: ${node.outerHtml}'); |
| 291 | 290 |
| 291 removeShadowRootPolyfill(node); | |
| 292 | |
| 292 String currentText = node.text; | 293 String currentText = node.text; |
| 293 | 294 |
| 294 trySelection = new TrySelection(node, selection); | 295 trySelection = new TrySelection(node, selection); |
| 295 trySelection.updateText(currentText); | 296 trySelection.updateText(currentText); |
| 296 | 297 |
| 297 editor.isMalformedInput = false; | 298 editor.isMalformedInput = false; |
| 298 int offset = 0; | 299 int offset = 0; |
| 299 List<Node> nodes = <Node>[]; | 300 List<Node> nodes = <Node>[]; |
| 300 | 301 |
| 301 String state = ''; | 302 String state = ''; |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 314 node.parent.insertAllBefore(nodes, node); | 315 node.parent.insertAllBefore(nodes, node); |
| 315 node.remove(); | 316 node.remove(); |
| 316 trySelection.adjust(selection); | 317 trySelection.adjust(selection); |
| 317 | 318 |
| 318 // Discard highlighting mutations. | 319 // Discard highlighting mutations. |
| 319 observer.takeRecords(); | 320 observer.takeRecords(); |
| 320 return; | 321 return; |
| 321 } | 322 } |
| 322 } | 323 } |
| 323 | 324 |
| 325 removeShadowRootPolyfill(mainEditorPane); | |
| 326 | |
| 324 String currentText = mainEditorPane.text; | 327 String currentText = mainEditorPane.text; |
| 325 trySelection.updateText(currentText); | 328 trySelection.updateText(currentText); |
| 326 | 329 |
| 327 context.currentCompilationUnit.content = currentText; | 330 context.currentCompilationUnit.content = currentText; |
| 328 | 331 |
| 329 editor.seenIdentifiers = new Set<String>.from(mock.identifiers); | 332 editor.seenIdentifiers = new Set<String>.from(mock.identifiers); |
| 330 | 333 |
| 331 editor.isMalformedInput = false; | 334 editor.isMalformedInput = false; |
| 332 int offset = 0; | 335 int offset = 0; |
| 333 List<Node> nodes = <Node>[]; | 336 List<Node> nodes = <Node>[]; |
| (...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 528 event.preventDefault(); | 531 event.preventDefault(); |
| 529 move(1); | 532 move(1); |
| 530 } | 533 } |
| 531 | 534 |
| 532 void moveUp(Event event) { | 535 void moveUp(Event event) { |
| 533 event.preventDefault(); | 536 event.preventDefault(); |
| 534 move(-1); | 537 move(-1); |
| 535 } | 538 } |
| 536 | 539 |
| 537 void move(int direction) { | 540 void move(int direction) { |
| 538 Element element = editor.moveActive(direction); | 541 Element element = editor.moveActive(direction, ui); |
| 539 if (element == null) return; | 542 if (element == null) return; |
| 540 var text = activeCompletion.firstChild; | 543 var text = activeCompletion.firstChild; |
| 541 String prefix = ""; | 544 String prefix = ""; |
| 542 if (text is Text) prefix = text.data.trim(); | 545 if (text is Text) prefix = text.data.trim(); |
| 543 updateInlineSuggestion(prefix, element.text); | 546 updateInlineSuggestion(prefix, element.text); |
| 544 } | 547 } |
| 545 | 548 |
| 546 void endCompletion({bool acceptSuggestion: false}) { | 549 void endCompletion({bool acceptSuggestion: false}) { |
| 547 if (acceptSuggestion) { | 550 if (acceptSuggestion) { |
| 548 suggestionAccepted(); | 551 suggestionAccepted(); |
| (...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 594 | 597 |
| 595 int anchorOffset = selection.anchorOffset; | 598 int anchorOffset = selection.anchorOffset; |
| 596 | 599 |
| 597 String prefix = text.data.substring(0, anchorOffset).trim(); | 600 String prefix = text.data.substring(0, anchorOffset).trim(); |
| 598 if (prefix.isEmpty) { | 601 if (prefix.isEmpty) { |
| 599 return endCompletion(); | 602 return endCompletion(); |
| 600 } | 603 } |
| 601 | 604 |
| 602 num height = activeCompletion.getBoundingClientRect().height; | 605 num height = activeCompletion.getBoundingClientRect().height; |
| 603 activeCompletion.classes.add('active'); | 606 activeCompletion.classes.add('active'); |
| 604 ui.nodes.clear(); | 607 Node root = getShadowRoot(ui); |
| 605 | 608 |
| 606 inline = new SpanElement() | 609 inline = new SpanElement() |
| 607 ..classes.add('hazed-suggestion'); | 610 ..classes.add('hazed-suggestion'); |
| 608 Text rest = text.splitText(anchorOffset); | 611 Text rest = text.splitText(anchorOffset); |
| 609 text.parentNode.insertBefore(inline, text.nextNode); | 612 text.parentNode.insertBefore(inline, text.nextNode); |
| 610 activeCompletion.parentNode.insertBefore( | 613 activeCompletion.parentNode.insertBefore( |
| 611 rest, activeCompletion.nextNode); | 614 rest, activeCompletion.nextNode); |
| 612 | 615 |
| 613 staticResults = new DivElement() | 616 staticResults = new DivElement() |
| 614 ..classes.addAll(['dart-static', 'dart-limited-height']); | 617 ..classes.addAll(['dart-static', 'dart-limited-height']); |
| 615 serverResults = new DivElement() | 618 serverResults = new DivElement() |
| 616 ..style.display = 'none' | 619 ..style.display = 'none' |
| 617 ..classes.add('dart-server'); | 620 ..classes.add('dart-server'); |
| 618 ui.nodes.addAll([staticResults, serverResults]); | 621 root.nodes.addAll([staticResults, serverResults]); |
| 619 ui.style.top = '${height}px'; | 622 ui.style.top = '${height}px'; |
| 620 | 623 |
| 621 staticResults.nodes.add(buildCompletionEntry(prefix)); | 624 staticResults.nodes.add(buildCompletionEntry(prefix)); |
| 622 | 625 |
| 623 updateSuggestions(prefix); | 626 updateSuggestions(prefix); |
| 624 } | 627 } |
| 625 | 628 |
| 626 void updateInlineSuggestion(String prefix, String suggestion) { | 629 void updateInlineSuggestion(String prefix, String suggestion) { |
| 627 inlineSuggestion = suggestion; | 630 inlineSuggestion = suggestion; |
| 628 | 631 |
| 629 minWidth = max(minWidth, activeCompletion.getBoundingClientRect().width); | 632 minWidth = max(minWidth, activeCompletion.getBoundingClientRect().width); |
| 630 | 633 |
| 631 activeCompletion.style | 634 activeCompletion.style |
| 632 ..display = 'inline-block' | 635 ..display = 'inline-block' |
| 633 ..minWidth = '${minWidth}px'; | 636 ..minWidth = '${minWidth}px'; |
| 634 | 637 |
| 635 inline | 638 setShadowRoot(inline, suggestion.substring(prefix.length)); |
| 636 ..nodes.clear() | 639 inline.style.display = ''; |
| 637 ..appendText(suggestion.substring(prefix.length)) | |
| 638 ..style.display = ''; | |
| 639 | 640 |
| 640 observer.takeRecords(); // Discard mutations. | 641 observer.takeRecords(); // Discard mutations. |
| 641 } | 642 } |
| 642 | 643 |
| 643 void updateSuggestions(String prefix) { | 644 void updateSuggestions(String prefix) { |
| 644 if (prefix.isEmpty) { | 645 if (prefix.isEmpty) { |
| 645 return endCompletion(); | 646 return endCompletion(); |
| 646 } | 647 } |
| 647 | 648 |
| 648 Token first = tokenize(prefix); | 649 Token first = tokenize(prefix); |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 659 num height = ui.getBoundingClientRect().height - borderHeight; | 660 num height = ui.getBoundingClientRect().height - borderHeight; |
| 660 ui.style.minHeight = '${height}px'; | 661 ui.style.minHeight = '${height}px'; |
| 661 | 662 |
| 662 minWidth = | 663 minWidth = |
| 663 max(minWidth, activeCompletion.getBoundingClientRect().width); | 664 max(minWidth, activeCompletion.getBoundingClientRect().width); |
| 664 | 665 |
| 665 staticResults.nodes.clear(); | 666 staticResults.nodes.clear(); |
| 666 serverResults.nodes.clear(); | 667 serverResults.nodes.clear(); |
| 667 | 668 |
| 668 if (inlineSuggestion != null && inlineSuggestion.startsWith(prefix)) { | 669 if (inlineSuggestion != null && inlineSuggestion.startsWith(prefix)) { |
| 669 inline | 670 setShadowRoot(inline, inlineSuggestion.substring(prefix.length)); |
| 670 ..nodes.clear() | |
| 671 ..appendText(inlineSuggestion.substring(prefix.length)); | |
| 672 } | 671 } |
| 673 | 672 |
| 674 List<String> results = editor.seenIdentifiers.where( | 673 List<String> results = editor.seenIdentifiers.where( |
| 675 (String identifier) { | 674 (String identifier) { |
| 676 return identifier != prefix && identifier.startsWith(prefix); | 675 return identifier != prefix && identifier.startsWith(prefix); |
| 677 }).toList(growable: false); | 676 }).toList(growable: false); |
| 678 results.sort(); | 677 results.sort(); |
| 679 if (results.isEmpty) results = <String>[prefix]; | 678 if (results.isEmpty) results = <String>[prefix]; |
| 680 | 679 |
| 681 results.forEach((String completion) { | 680 results.forEach((String completion) { |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 696 List<String> serverSuggestions = JSON.decode(responseText); | 695 List<String> serverSuggestions = JSON.decode(responseText); |
| 697 if (!serverSuggestions.isEmpty) { | 696 if (!serverSuggestions.isEmpty) { |
| 698 updateInlineSuggestion(prefix, serverSuggestions.first); | 697 updateInlineSuggestion(prefix, serverSuggestions.first); |
| 699 } | 698 } |
| 700 for (int i = 1; i < serverSuggestions.length; i++) { | 699 for (int i = 1; i < serverSuggestions.length; i++) { |
| 701 String completion = serverSuggestions[i]; | 700 String completion = serverSuggestions[i]; |
| 702 DivElement where = staticResults; | 701 DivElement where = staticResults; |
| 703 int index = results.indexOf(completion); | 702 int index = results.indexOf(completion); |
| 704 if (index != -1) { | 703 if (index != -1) { |
| 705 List<Element> entries = | 704 List<Element> entries = |
| 706 document.querySelectorAll('.dart-static>.dart-entry'); | 705 getShadowRoot(ui).querySelectorAll( |
|
kasperl
2014/05/06 16:58:30
Cache the shadowRoot in a local outside the loop?
ahe
2014/05/08 07:37:23
Done.
| |
| 706 '.dart-static>.dart-entry'); | |
| 707 entries[index].classes.add('doubleplusgood'); | 707 entries[index].classes.add('doubleplusgood'); |
| 708 } else { | 708 } else { |
| 709 if (results.length > 3) { | 709 if (results.length > 3) { |
| 710 serverResults.style.display = 'block'; | 710 serverResults.style.display = 'block'; |
| 711 where = serverResults; | 711 where = serverResults; |
| 712 } | 712 } |
| 713 Element entry = buildCompletionEntry(completion); | 713 Element entry = buildCompletionEntry(completion); |
| 714 entry.classes.add('doubleplusgood'); | 714 entry.classes.add('doubleplusgood'); |
| 715 where.nodes.add(entry); | 715 where.nodes.add(entry); |
| 716 } | 716 } |
| (...skipping 166 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 883 Node line = findLine(text); | 883 Node line = findLine(text); |
| 884 return | 884 return |
| 885 line.nextNode == null && | 885 line.nextNode == null && |
| 886 text.parent.nextNode == null && | 886 text.parent.nextNode == null && |
| 887 offset == text.length; | 887 offset == text.length; |
| 888 } | 888 } |
| 889 | 889 |
| 890 List<String> splitLines(String text) { | 890 List<String> splitLines(String text) { |
| 891 return text.split(new RegExp('^', multiLine: true)); | 891 return text.split(new RegExp('^', multiLine: true)); |
| 892 } | 892 } |
| 893 | |
| 894 void removeCodeCompletion() { | |
| 895 List<Node> highlighting = | |
| 896 mainEditorPane.querySelectorAll('.dart-code-completion'); | |
| 897 for (Element element in highlighting) { | |
| 898 element.remove(); | |
| 899 } | |
| 900 } | |
| OLD | NEW |