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 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 85 | 85 |
| 86 // Implementation note: The state machine is actually implemented by | 86 // Implementation note: The state machine is actually implemented by |
| 87 // [InteractionContext], this class represents public event handlers. | 87 // [InteractionContext], this class represents public event handlers. |
| 88 | 88 |
| 89 factory InteractionManager() => new InteractionContext(); | 89 factory InteractionManager() => new InteractionContext(); |
| 90 | 90 |
| 91 InteractionManager.internal(); | 91 InteractionManager.internal(); |
| 92 | 92 |
| 93 void onInput(Event event); | 93 void onInput(Event event); |
| 94 | 94 |
| 95 // TODO(ahe): Rename to onKeyDown (as it is called in response to keydown | |
| 96 // event). | |
| 95 void onKeyUp(KeyboardEvent event); | 97 void onKeyUp(KeyboardEvent event); |
| 96 | 98 |
| 97 void onMutation(List<MutationRecord> mutations, MutationObserver observer); | 99 void onMutation(List<MutationRecord> mutations, MutationObserver observer); |
| 98 | 100 |
| 99 void onSelectionChange(Event event); | 101 void onSelectionChange(Event event); |
| 100 | 102 |
| 101 /// Called when the content of a CompilationUnit changed. | 103 /// Called when the content of a CompilationUnit changed. |
| 102 void onCompilationUnitChanged(CompilationUnit unit); | 104 void onCompilationUnitChanged(CompilationUnit unit); |
| 103 | 105 |
| 104 Future<List<String>> projectFileNames(); | 106 Future<List<String>> projectFileNames(); |
| (...skipping 24 matching lines...) Expand all Loading... | |
| 129 state = new InitialState(this); | 131 state = new InitialState(this); |
| 130 } | 132 } |
| 131 | 133 |
| 132 void onInput(Event event) => state.onInput(event); | 134 void onInput(Event event) => state.onInput(event); |
| 133 | 135 |
| 134 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); | 136 void onKeyUp(KeyboardEvent event) => state.onKeyUp(event); |
| 135 | 137 |
| 136 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 138 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
| 137 try { | 139 try { |
| 138 try { | 140 try { |
| 139 return state.onMutation(mutations, observer); | 141 Stopwatch sw = new Stopwatch()..start(); |
| 142 state.onMutation(mutations, observer); | |
| 143 sw.stop(); | |
| 144 print('onMutation took ${sw.elapsedMilliseconds}ms'); | |
|
kasperl
2014/05/06 04:51:06
Do you always want to do this?
ahe
2014/05/06 12:54:02
Removed.
| |
| 140 } finally { | 145 } finally { |
| 141 // Discard any mutations during the observer, as these can lead to | 146 // Discard any mutations during the observer, as these can lead to |
| 142 // infinite loop. | 147 // infinite loop. |
| 143 observer.takeRecords(); | 148 observer.takeRecords(); |
| 144 } | 149 } |
| 145 } catch (error, stackTrace) { | 150 } catch (error, stackTrace) { |
| 146 try { | 151 try { |
| 147 editor.isMalformedInput = true; | 152 editor.isMalformedInput = true; |
| 148 outputDiv | 153 outputDiv |
| 149 ..nodes.clear() | 154 ..nodes.clear() |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 221 onUnmodifiedKeyUp(event); | 226 onUnmodifiedKeyUp(event); |
| 222 } | 227 } |
| 223 } | 228 } |
| 224 | 229 |
| 225 void onModifiedKeyUp(KeyboardEvent event) { | 230 void onModifiedKeyUp(KeyboardEvent event) { |
| 226 } | 231 } |
| 227 | 232 |
| 228 void onUnmodifiedKeyUp(KeyboardEvent event) { | 233 void onUnmodifiedKeyUp(KeyboardEvent event) { |
| 229 switch (event.keyCode) { | 234 switch (event.keyCode) { |
| 230 case KeyCode.ENTER: { | 235 case KeyCode.ENTER: { |
| 231 event.preventDefault(); | |
| 232 Selection selection = window.getSelection(); | 236 Selection selection = window.getSelection(); |
| 233 if (isCollapsed(selection) && selection.anchorNode is Text) { | 237 if (isCollapsed(selection)) { |
| 234 Text text = selection.anchorNode; | 238 event.preventDefault(); |
| 235 int offset = selection.anchorOffset; | 239 Node node = selection.anchorNode; |
| 236 text.insertData(offset, '\n'); | 240 if (node is Text) { |
| 237 selection.collapse(text, offset + 1); | 241 Text text = node; |
| 242 int offset = selection.anchorOffset; | |
| 243 // If at end-of-file, insert an extra newline. | |
| 244 String newline = isAtEndOfFile(text, offset) ? '\n\n' : '\n'; | |
| 245 text.insertData(offset, newline); | |
| 246 selection.collapse(text, offset + 1); | |
| 247 } else if (node is Element) { | |
| 248 node.appendText('\n\n'); | |
|
kasperl
2014/05/06 04:51:06
A general comment that explains why you're adding
ahe
2014/05/06 12:54:02
Done.
| |
| 249 selection.collapse(node.firstChild, 1); | |
| 250 } else { | |
| 251 window.console.error('Unexpected node'); | |
| 252 window.console.dir(node); | |
|
kasperl
2014/05/06 04:51:06
Use cascades?
ahe
2014/05/06 12:54:02
Done.
| |
| 253 } | |
| 238 } | 254 } |
| 239 break; | 255 break; |
| 240 } | 256 } |
| 241 } | 257 } |
| 242 | 258 |
| 243 // editor.scheduleRemoveCodeCompletion(); | 259 // editor.scheduleRemoveCodeCompletion(); |
| 244 | 260 |
| 245 // This is a hack to get Safari (iOS) to send mutation events on | 261 // This is a hack to get Safari (iOS) to send mutation events on |
| 246 // contenteditable. | 262 // contenteditable. |
| 247 // TODO(ahe): Move to onInput? | 263 // TODO(ahe): Move to onInput? |
| 248 var newDiv = new DivElement(); | 264 var newDiv = new DivElement(); |
| 249 hackDiv.replaceWith(newDiv); | 265 hackDiv.replaceWith(newDiv); |
| 250 hackDiv = newDiv; | 266 hackDiv = newDiv; |
| 251 } | 267 } |
| 252 | 268 |
| 253 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { | 269 void onMutation(List<MutationRecord> mutations, MutationObserver observer) { |
| 254 print('onMutation'); | 270 print('onMutation'); |
| 255 | 271 |
| 256 List<Node> highlighting = mainEditorPane.querySelectorAll( | 272 List<Node> highlighting = mainEditorPane.querySelectorAll( |
| 257 'a.diagnostic>span, .dart-code-completion, .hazed-suggestion'); | 273 'a.diagnostic>span, .dart-code-completion, .hazed-suggestion'); |
| 258 for (Element element in highlighting) { | 274 for (Element element in highlighting) { |
| 259 element.remove(); | 275 element.remove(); |
| 260 } | 276 } |
| 261 | 277 |
| 262 Selection selection = window.getSelection(); | 278 Selection selection = window.getSelection(); |
| 263 TrySelection trySelection = new TrySelection(mainEditorPane, selection); | 279 TrySelection trySelection = new TrySelection(mainEditorPane, selection); |
| 264 | 280 |
| 281 Set<Node> normalizedNodes = new Set<Node>(); | |
| 265 for (MutationRecord record in mutations) { | 282 for (MutationRecord record in mutations) { |
| 266 normalizeMutationRecord(record, trySelection); | 283 normalizeMutationRecord(record, trySelection, normalizedNodes); |
| 284 } | |
| 285 | |
| 286 if (normalizedNodes.length == 1) { | |
| 287 Node node = normalizedNodes.single; | |
| 288 if (node is Element && node.classes.contains('lineNumber')) { | |
| 289 print('Single line change: ${node.outerHtml}'); | |
|
kasperl
2014/05/06 04:51:06
Do you want to keep the printing in here?
ahe
2014/05/06 12:54:02
For now, yes.
| |
| 290 | |
| 291 String currentText = node.text; | |
| 292 | |
| 293 trySelection = new TrySelection(node, selection); | |
| 294 trySelection.updateText(currentText); | |
| 295 | |
| 296 editor.isMalformedInput = false; | |
| 297 int offset = 0; | |
| 298 List<Node> nodes = <Node>[]; | |
| 299 | |
| 300 String state = ''; | |
| 301 Element previousLine = node.previousElementSibling; | |
| 302 if (previousLine != null) { | |
| 303 state = previousLine.getAttribute('dart-state'); | |
| 304 } | |
| 305 for (String line in currentText.split(new RegExp('^', multiLine: true))) { | |
|
kasperl
2014/05/06 04:51:06
Long line. Consider adding a local for the result
ahe
2014/05/06 12:54:02
I created a method for splitting text into lines.
| |
| 306 List<Node> lineNodes = <Node>[]; | |
| 307 state = | |
| 308 tokenizeAndHighlight(line, state, offset, trySelection, lineNodes) ; | |
|
kasperl
2014/05/06 04:51:06
Long line.
ahe
2014/05/06 12:54:02
Done.
| |
| 309 offset += line.length; | |
| 310 nodes.add(makeLine(lineNodes, state)); | |
| 311 } | |
| 312 | |
| 313 node.parent.insertAllBefore(nodes, node); | |
| 314 node.remove(); | |
| 315 trySelection.adjust(selection); | |
| 316 | |
| 317 // Discard highlighting mutations. | |
| 318 observer.takeRecords(); | |
| 319 return; | |
| 320 } | |
| 267 } | 321 } |
| 268 | 322 |
| 269 String currentText = mainEditorPane.text; | 323 String currentText = mainEditorPane.text; |
| 270 trySelection.updateText(currentText); | 324 trySelection.updateText(currentText); |
| 271 | 325 |
| 272 context.currentCompilationUnit.content = currentText; | 326 context.currentCompilationUnit.content = currentText; |
| 273 | 327 |
| 274 editor.seenIdentifiers = new Set<String>.from(mock.identifiers); | 328 editor.seenIdentifiers = new Set<String>.from(mock.identifiers); |
| 275 | 329 |
| 276 editor.isMalformedInput = false; | 330 editor.isMalformedInput = false; |
| 277 int offset = 0; | 331 int offset = 0; |
| 278 List<Node> nodes = <Node>[]; | 332 List<Node> nodes = <Node>[]; |
| 279 | 333 |
| 280 String state = ''; | 334 String state = ''; |
| 281 for (String line in currentText.split(new RegExp('^', multiLine: true))) { | 335 for (String line in currentText.split(new RegExp('^', multiLine: true))) { |
| 282 List<Node> lineNodes = <Node>[]; | 336 List<Node> lineNodes = <Node>[]; |
| 283 state = | 337 state = |
| 284 tokenizeAndHighlight(line, state, offset, trySelection, lineNodes); | 338 tokenizeAndHighlight(line, state, offset, trySelection, lineNodes); |
| 285 offset += line.length; | 339 offset += line.length; |
| 286 nodes.add(new SpanElement() | 340 nodes.add(makeLine(lineNodes, state)); |
| 287 ..nodes.addAll(lineNodes) | |
| 288 ..classes.add('lineNumber')); | |
| 289 } | 341 } |
| 290 | 342 |
| 291 mainEditorPane | 343 mainEditorPane |
| 292 ..nodes.clear() | 344 ..nodes.clear() |
| 293 ..nodes.addAll(nodes); | 345 ..nodes.addAll(nodes); |
| 294 trySelection.adjust(selection); | 346 trySelection.adjust(selection); |
| 295 | 347 |
| 296 // Discard highlighting mutations. | 348 // Discard highlighting mutations. |
| 297 observer.takeRecords(); | 349 observer.takeRecords(); |
| 298 } | 350 } |
| (...skipping 479 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 778 | 830 |
| 779 bool isUnterminatedMultiLineToken(UnterminatedToken token) { | 831 bool isUnterminatedMultiLineToken(UnterminatedToken token) { |
| 780 return | 832 return |
| 781 token.start == '/*' || | 833 token.start == '/*' || |
| 782 token.start == "'''" || | 834 token.start == "'''" || |
| 783 token.start == '"""' || | 835 token.start == '"""' || |
| 784 token.start == "r'''" || | 836 token.start == "r'''" || |
| 785 token.start == 'r"""'; | 837 token.start == 'r"""'; |
| 786 } | 838 } |
| 787 | 839 |
| 788 void normalizeMutationRecord(MutationRecord record, TrySelection selection) { | 840 void normalizeMutationRecord(MutationRecord record, |
| 789 if (record.addedNodes.isEmpty) return; | 841 TrySelection selection, |
| 842 Set<Node> normalizedNodes) { | |
| 790 for (Node node in record.addedNodes) { | 843 for (Node node in record.addedNodes) { |
| 791 if (node.parent == null) continue; | 844 if (node.parent == null) continue; |
| 792 StringBuffer buffer = new StringBuffer(); | 845 StringBuffer buffer = new StringBuffer(); |
| 793 int selectionOffset = htmlToText(node, buffer, selection); | 846 int selectionOffset = htmlToText(node, buffer, selection); |
| 794 Text newNode = new Text('$buffer'); | 847 Text newNode = new Text('$buffer'); |
| 795 node.replaceWith(newNode); | 848 node.replaceWith(newNode); |
| 849 normalizedNodes.add(findLine(newNode)); | |
| 796 if (selectionOffset != -1) { | 850 if (selectionOffset != -1) { |
| 797 selection.anchorNode = newNode; | 851 selection.anchorNode = newNode; |
| 798 selection.anchorOffset = selectionOffset; | 852 selection.anchorOffset = selectionOffset; |
| 799 } | 853 } |
| 800 } | 854 } |
| 855 if (!record.removedNodes.isEmpty) { | |
| 856 normalizedNodes.add(findLine(record.target)); | |
| 857 } | |
| 858 if (record.type == "characterData") { | |
| 859 normalizedNodes.add(findLine(record.target)); | |
| 860 } | |
| 801 } | 861 } |
| 862 | |
| 863 // Finds the line of [node] (a parent node with CSS class 'lineNumber'). | |
| 864 // If no such parent exists, return mainEditorPane if it is a parent. | |
| 865 // Otherwise return [node]. | |
| 866 Node findLine(Node node) { | |
| 867 for (Node n = node; n != null; n = n.parent) { | |
| 868 if (n is Element && n.classes.contains('lineNumber')) return n; | |
| 869 if (n == mainEditorPane) return n; | |
| 870 } | |
| 871 return node; | |
| 872 } | |
| 873 | |
| 874 Element makeLine(List<Node> lineNodes, String state) { | |
| 875 // Using a div element here (anything with display=block) generally messes up | |
| 876 // editing and navigation. We would like to use a block element here so | |
| 877 // error messages show as expected. But no such luck. Fortunately, there | |
| 878 // are strong indications that the current solution for displaying errors | |
| 879 // isn't good enough anyways. | |
| 880 return new SpanElement() | |
| 881 ..setAttribute('dart-state', state) | |
| 882 ..nodes.addAll(lineNodes) | |
| 883 ..classes.add('lineNumber'); | |
| 884 } | |
| 885 | |
| 886 bool isAtEndOfFile(Text text, int offset) { | |
| 887 // return false; | |
|
kasperl
2014/05/06 04:51:06
Remove this comment.
ahe
2014/05/06 12:54:02
Done.
| |
| 888 Node line = findLine(text); | |
| 889 return | |
| 890 line.nextNode == null && | |
| 891 text.parent.nextNode == null && | |
| 892 offset == text.length; | |
| 893 } | |
| OLD | NEW |